/**
 * @version 0.5
 *
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (c) 2005 - 2012 Jean-Francois Hovinne, http://www.wymeditor.org/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http://www.wymeditor.org/
 *
 * File: jquery.wymeditor.js
 *
 *        Main JS file with core classes and functions.
 *        See the documentation for more info.
 *
 * About: authors
 *
 *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
 *        Volker Mische (vmx a-t gmx dotde)
 *        Scott Lewis (lewiscot a-t gmail dotcom)
 *        Bermi Ferrer (wymeditor a-t bermi dotorg)
 *        Daniel Reszka (d.reszka a-t wymeditor dotorg)
 *        Jonatan Lundin (jonatan.lundin a-t gmail dotcom)
 */

/*
 Namespace: WYMeditor
 Global WYMeditor namespace.
 */
if (!WYMeditor)
    var WYMeditor = {};

//Wrap the Firebug console in WYMeditor.console
(function() {
    if (!window.console || !console.firebug) {
        var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
            "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];

        WYMeditor.console = {};
        for (var i = 0; i < names.length; ++i)
            WYMeditor.console[names[i]] = function() {
            }

    } else
        WYMeditor.console = window.console;
})();

jQuery.extend(WYMeditor, {
    /*
     Constants: Global WYMeditor constants.
     
     VERSION             - Defines WYMeditor version.
     INSTANCES           - An array of loaded WYMeditor.editor instances.
     STRINGS             - An array of loaded WYMeditor language pairs/values.
     SKINS               - An array of loaded WYMeditor skins.
     NAME                - The "name" attribute.
     INDEX               - A string replaced by the instance index.
     WYM_INDEX           - A string used to get/set the instance index.
     BASE_PATH           - A string replaced by WYMeditor's base path.
     SKIN_PATH           - A string replaced by WYMeditor's skin path.
     WYM_PATH            - A string replaced by WYMeditor's main JS file path.
     SKINS_DEFAULT_PATH  - The skins default base path.
     SKINS_DEFAULT_CSS   - The skins default CSS file.
     LANG_DEFAULT_PATH   - The language files default path.
     IFRAME_BASE_PATH    - A string replaced by the designmode iframe's base path.
     IFRAME_DEFAULT      - The iframe's default base path.
     JQUERY_PATH         - A string replaced by the computed jQuery path.
     DIRECTION           - A string replaced by the text direction (rtl or ltr).
     LOGO                - A string replaced by WYMeditor logo.
     TOOLS               - A string replaced by the toolbar's HTML.
     TOOLS_ITEMS         - A string replaced by the toolbar items.
     TOOL_NAME           - A string replaced by a toolbar item's name.
     TOOL_TITLE          - A string replaced by a toolbar item's title.
     TOOL_CLASS          - A string replaced by a toolbar item's class.
     CLASSES             - A string replaced by the classes panel's HTML.
     CLASSES_ITEMS       - A string replaced by the classes items.
     CLASS_NAME          - A string replaced by a class item's name.
     CLASS_TITLE         - A string replaced by a class item's title.
     CONTAINERS          - A string replaced by the containers panel's HTML.
     CONTAINERS_ITEMS    - A string replaced by the containers items.
     CONTAINER_NAME      - A string replaced by a container item's name.
     CONTAINER_TITLE     - A string replaced by a container item's title.
     CONTAINER_CLASS     - A string replaced by a container item's class.
     HTML                - A string replaced by the HTML view panel's HTML.
     IFRAME              - A string replaced by the designmode iframe.
     STATUS              - A string replaced by the status panel's HTML.
     DIALOG_TITLE        - A string replaced by a dialog's title.
     DIALOG_BODY         - A string replaced by a dialog's HTML body.
     BODY                - The BODY element.
     STRING              - The "string" type.
     BODY,DIV,P,
     H1,H2,H3,H4,H5,H6,
     PRE,BLOCKQUOTE,
     A,BR,IMG,
     TABLE,TD,TH,
     UL,OL,LI            - HTML elements string representation.
     CLASS,HREF,SRC,
     TITLE,ALT           - HTML attributes string representation.
     DIALOG_LINK         - A link dialog type.
     DIALOG_IMAGE        - An image dialog type.
     DIALOG_TABLE        - A table dialog type.
     DIALOG_PASTE        - A 'Paste from Word' dialog type.
     BOLD                - Command: (un)set selection to <strong>.
     ITALIC              - Command: (un)set selection to <em>.
     CREATE_LINK         - Command: open the link dialog or (un)set link.
     INSERT_IMAGE        - Command: open the image dialog or insert an image.
     INSERT_TABLE        - Command: open the table dialog.
     PASTE               - Command: open the paste dialog.
     INDENT              - Command: nest a list item.
     OUTDENT             - Command: unnest a list item.
     TOGGLE_HTML         - Command: display/hide the HTML view.
     FORMAT_BLOCK        - Command: set a block element to another type.
     PREVIEW             - Command: open the preview dialog.
     UNLINK              - Command: unset a link.
     INSERT_UNORDEREDLIST- Command: insert an unordered list.
     INSERT_ORDEREDLIST  - Command: insert an ordered list.
     MAIN_CONTAINERS     - An array of the main HTML containers used in WYMeditor.
     BLOCKS              - An array of the HTML block elements.
     KEY                 - Standard key codes.
     NODE                - Node types.
     
     */

    VERSION: "0.5",
    INSTANCES: [],
    STRINGS: [],
    SKINS: [],
    NAME: "name",
    INDEX: "{Wym_Index}",
    WYM_INDEX: "wym_index",
    BASE_PATH: "{Wym_Base_Path}",
    CSS_PATH: "{Wym_Css_Path}",
    WYM_PATH: "{Wym_Wym_Path}",
    SKINS_DEFAULT_PATH: "skins/",
    SKINS_DEFAULT_CSS: "skin.css",
    SKINS_DEFAULT_JS: "skin.js",
    LANG_DEFAULT_PATH: "lang/",
    IFRAME_BASE_PATH: "{Wym_Iframe_Base_Path}",
    IFRAME_DEFAULT: "iframe/default/",
    JQUERY_PATH: "{Wym_Jquery_Path}",
    DIRECTION: "{Wym_Direction}",
    LOGO: "{Wym_Logo}",
    TOOLS: "{Wym_Tools}",
    TOOLS_ITEMS: "{Wym_Tools_Items}",
    TOOL_NAME: "{Wym_Tool_Name}",
    TOOL_TITLE: "{Wym_Tool_Title}",
    TOOL_CLASS: "{Wym_Tool_Class}",
    CLASSES: "{Wym_Classes}",
    CLASSES_ITEMS: "{Wym_Classes_Items}",
    CLASS_NAME: "{Wym_Class_Name}",
    CLASS_TITLE: "{Wym_Class_Title}",
    CONTAINERS: "{Wym_Containers}",
    CONTAINERS_ITEMS: "{Wym_Containers_Items}",
    CONTAINER_NAME: "{Wym_Container_Name}",
    CONTAINER_TITLE: "{Wym_Containers_Title}",
    CONTAINER_CLASS: "{Wym_Container_Class}",
    HTML: "{Wym_Html}",
    IFRAME: "{Wym_Iframe}",
    STATUS: "{Wym_Status}",
    DIALOG_TITLE: "{Wym_Dialog_Title}",
    DIALOG_BODY: "{Wym_Dialog_Body}",
    STRING: "string",
    BODY: "body",
    DIV: "div",
    P: "p",
    H1: "h1",
    H2: "h2",
    H3: "h3",
    H4: "h4",
    H5: "h5",
    H6: "h6",
    PRE: "pre",
    BLOCKQUOTE: "blockquote",
    A: "a",
    BR: "br",
    IMG: "img",
    TABLE: "table",
    TD: "td",
    TH: "th",
    UL: "ul",
    OL: "ol",
    LI: "li",
    CLASS: "class",
    HREF: "href",
    SRC: "src",
    TITLE: "title",
    ALT: "alt",
    DIALOG_LINK: "Link",
    DIALOG_IMAGE: "Image",
    DIALOG_TABLE: "Table",
    DIALOG_PASTE: "Paste_From_Word",
    BOLD: "Bold",
    ITALIC: "Italic",
    CREATE_LINK: "CreateLink",
    INSERT_IMAGE: "InsertImage",
    INSERT_TABLE: "InsertTable",
    INSERT_HTML: "InsertHTML",
    PASTE: "Paste",
    INDENT: "Indent",
    OUTDENT: "Outdent",
    TOGGLE_HTML: "ToggleHtml",
    FORMAT_BLOCK: "FormatBlock",
    PREVIEW: "Preview",
    UNLINK: "Unlink",
    INSERT_UNORDEREDLIST: "InsertUnorderedList",
    INSERT_ORDEREDLIST: "InsertOrderedList",
    MAIN_CONTAINERS: new Array("p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "blockquote"),
    BLOCKS: new Array("address", "blockquote", "div", "dl",
            "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
            "noscript", "ol", "p", "pre", "table", "ul", "dd", "dt",
            "li", "tbody", "td", "tfoot", "th", "thead", "tr"),
    KEY: {
        BACKSPACE: 8,
        ENTER: 13,
        END: 35,
        HOME: 36,
        LEFT: 37,
        UP: 38,
        RIGHT: 39,
        DOWN: 40,
        CURSOR: new Array(37, 38, 39, 40),
        DELETE: 46
    },
    NODE: {
        ELEMENT: 1,
        ATTRIBUTE: 2,
        TEXT: 3
    },
    /*
     Class: WYMeditor.editor
     WYMeditor editor main class, instanciated for each editor occurrence.
     */

    editor: function(elem, options) {

        /*
         Constructor: WYMeditor.editor
         
         Initializes main values (index, elements, paths, ...)
         and call WYMeditor.editor.init which initializes the editor.
         
         Parameters:
         
         elem - The HTML element to be replaced by the editor.
         options - The hash of options.
         
         Returns:
         
         Nothing.
         
         See Also:
         
         <WYMeditor.editor.init>
         */

        //store the instance in the INSTANCES array and store the index
        this._index = WYMeditor.INSTANCES.push(this) - 1;
        //store the element replaced by the editor
        this._element = elem;
        //store the options
        this._options = options;
        //store the element's inner value
        this._html = jQuery(elem).val();

        //store the HTML option, if any
        if (this._options.html)
            this._html = this._options.html;
        //get or compute the base path (where the main JS file is located)
        this._options.basePath = this._options.basePath
                || this.computeBasePath();
        //get or set the skin path (where the skin files are located)
        this._options.skinPath = this._options.skinPath
                || this._options.basePath + WYMeditor.SKINS_DEFAULT_PATH
                + this._options.skin + '/';
        //get or compute the main JS file location
        this._options.wymPath = this._options.wymPath
                || this.computeWymPath();
        //get or set the language files path
        this._options.langPath = this._options.langPath
                || this._options.basePath + WYMeditor.LANG_DEFAULT_PATH;
        //get or set the designmode iframe's base path
        this._options.iframeBasePath = this._options.iframeBasePath
                || this._options.basePath + WYMeditor.IFRAME_DEFAULT;
        //get or compute the jQuery JS file location
        this._options.jQueryPath = this._options.jQueryPath
                || this.computeJqueryPath();

        //initialize the editor instance
        this.init();

    }

});


/********** JQUERY **********/

/**
 * Replace an HTML element by WYMeditor
 *
 * @example jQuery(".wymeditor").wymeditor(
 *        {
 *
 *        }
 *      );
 * @desc Example description here
 * 
 * @name WYMeditor
 * @description WYMeditor is a web-based WYSIWYM XHTML editor
 * @param Hash hash A hash of parameters
 * @option Integer iExample Description here
 * @option String sExample Description here
 *
 * @type jQuery
 * @cat Plugins/WYMeditor
 * @author Jean-Francois Hovinne
 */
jQuery.fn.wymeditor = function(options) {

    options = jQuery.extend({
        html: "",
        basePath: false,
        skinPath: false,
        wymPath: false,
        iframeBasePath: false,
        jQueryPath: false,
        styles: false,
        stylesheet: false,
        skin: "default",
        initSkin: true,
        loadSkin: true,
        lang: "en",
        direction: "ltr",
        boxHtml: "<div class='wym_box'>"
                + "<div class='wym_area_top'>"
                + WYMeditor.TOOLS
                + "</div>"
                + "<div class='wym_area_left'></div>"
                + "<div class='wym_area_right'>"
                + WYMeditor.CONTAINERS
                + WYMeditor.CLASSES
                + "</div>"
                + "<div class='wym_area_main'>"
                + WYMeditor.HTML
                + WYMeditor.IFRAME
                + WYMeditor.STATUS
                + "</div>"
                + "<div class='wym_area_bottom'>"
                + WYMeditor.LOGO
                + "</div>"
                + "</div>",
        logoHtml: "<a class='wym_wymeditor_link' "
                + "href='http://www.wymeditor.org/'>WYMeditor</a>",
        iframeHtml: "<div class='wym_iframe wym_section'>"
                + "<iframe "
                + "src='"
                + WYMeditor.IFRAME_BASE_PATH
                + "wymiframe.html' "
                + "onload='this.contentWindow.parent.WYMeditor.INSTANCES["
                + WYMeditor.INDEX + "].initIframe(this)'"
                + "></iframe>"
                + "</div>",
        editorStyles: [],
        toolsHtml: "<div class='wym_tools wym_section'>"
                + "<h2>{Tools}</h2>"
                + "<ul>"
                + WYMeditor.TOOLS_ITEMS
                + "</ul>"
                + "</div>",
        toolsItemHtml: "<li class='"
                + WYMeditor.TOOL_CLASS
                + "'><a href='#' name='"
                + WYMeditor.TOOL_NAME
                + "' title='"
                + WYMeditor.TOOL_TITLE
                + "'>"
                + WYMeditor.TOOL_TITLE
                + "</a></li>",
        toolsItems: [
            {'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'},
            {'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'},
            {'name': 'Superscript', 'title': 'Superscript',
                'css': 'wym_tools_superscript'},
            {'name': 'Subscript', 'title': 'Subscript',
                'css': 'wym_tools_subscript'},
            {'name': 'InsertOrderedList', 'title': 'Ordered_List',
                'css': 'wym_tools_ordered_list'},
            {'name': 'InsertUnorderedList', 'title': 'Unordered_List',
                'css': 'wym_tools_unordered_list'},
            {'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'},
            {'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'},
            {'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'},
            {'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'},
            {'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'},
            {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
            {'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
            {'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
            {'name': 'Paste', 'title': 'Paste_From_Word',
                'css': 'wym_tools_paste'},
            {'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'},
            {'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}
        ],
        containersHtml: "<div class='wym_containers wym_section'>"
                + "<h2>{Containers}</h2>"
                + "<ul>"
                + WYMeditor.CONTAINERS_ITEMS
                + "</ul>"
                + "</div>",
        containersItemHtml: "<li class='"
                + WYMeditor.CONTAINER_CLASS
                + "'>"
                + "<a href='#' name='"
                + WYMeditor.CONTAINER_NAME
                + "'>"
                + WYMeditor.CONTAINER_TITLE
                + "</a></li>",
        containersItems: [
            {'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'},
            {'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'},
            {'name': 'H2', 'title': 'Heading_2', 'css': 'wym_containers_h2'},
            {'name': 'H3', 'title': 'Heading_3', 'css': 'wym_containers_h3'},
            {'name': 'H4', 'title': 'Heading_4', 'css': 'wym_containers_h4'},
            {'name': 'H5', 'title': 'Heading_5', 'css': 'wym_containers_h5'},
            {'name': 'H6', 'title': 'Heading_6', 'css': 'wym_containers_h6'},
            {'name': 'PRE', 'title': 'Preformatted', 'css': 'wym_containers_pre'},
            {'name': 'BLOCKQUOTE', 'title': 'Blockquote',
                'css': 'wym_containers_blockquote'},
            {'name': 'TH', 'title': 'Table_Header', 'css': 'wym_containers_th'}
        ],
        classesHtml: "<div class='wym_classes wym_section'>"
                + "<h2>{Classes}</h2><ul>"
                + WYMeditor.CLASSES_ITEMS
                + "</ul></div>",
        classesItemHtml: "<li class='wym_classes_"
                + WYMeditor.CLASS_NAME
                + "'><a href='#' name='"
                + WYMeditor.CLASS_NAME
                + "'>"
                + WYMeditor.CLASS_TITLE
                + "</a></li>",
        classesItems: [],
        statusHtml: "<div class='wym_status wym_section'>"
                + "<h2>{Status}</h2>"
                + "</div>",
        htmlHtml: "<div class='wym_html wym_section'>"
                + "<h2>{Source_Code}</h2>"
                + "<textarea class='wym_html_val'></textarea>"
                + "</div>",
        boxSelector: ".wym_box",
        toolsSelector: ".wym_tools",
        toolsListSelector: " ul",
        containersSelector: ".wym_containers",
        classesSelector: ".wym_classes",
        htmlSelector: ".wym_html",
        iframeSelector: ".wym_iframe iframe",
        iframeBodySelector: ".wym_iframe",
        statusSelector: ".wym_status",
        toolSelector: ".wym_tools a",
        containerSelector: ".wym_containers a",
        classSelector: ".wym_classes a",
        htmlValSelector: ".wym_html_val",
        hrefSelector: ".wym_href",
        srcSelector: ".wym_src",
        titleSelector: ".wym_title",
        altSelector: ".wym_alt",
        textSelector: ".wym_text",
        rowsSelector: ".wym_rows",
        colsSelector: ".wym_cols",
        captionSelector: ".wym_caption",
        summarySelector: ".wym_summary",
        submitSelector: ".wym_submit",
        cancelSelector: ".wym_cancel",
        previewSelector: "",
        dialogTypeSelector: ".wym_dialog_type",
        dialogLinkSelector: ".wym_dialog_link",
        dialogImageSelector: ".wym_dialog_image",
        dialogTableSelector: ".wym_dialog_table",
        dialogPasteSelector: ".wym_dialog_paste",
        dialogPreviewSelector: ".wym_dialog_preview",
        updateSelector: ".wymupdate",
        updateEvent: "click",
        dialogFeatures: "menubar=no,titlebar=no,toolbar=no,resizable=no"
                + ",width=560,height=300,top=0,left=0",
        dialogFeaturesPreview: "menubar=no,titlebar=no,toolbar=no,resizable=no"
                + ",scrollbars=yes,width=560,height=300,top=0,left=0",
        dialogHtml: "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'"
                + " 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>"
                + "<html dir='"
                + WYMeditor.DIRECTION
                + "'><head>"
                + "<link rel='stylesheet' type='text/css' media='screen'"
                + " href='"
                + WYMeditor.CSS_PATH
                + "' />"
                + "<title>"
                + WYMeditor.DIALOG_TITLE
                + "</title>"
                + "<script type='text/javascript'"
                + " src='"
                + WYMeditor.JQUERY_PATH
                + "'></script>"
                + "<script type='text/javascript'"
                + " src='"
                + WYMeditor.WYM_PATH
                + "'></script>"
                + "</head>"
                + WYMeditor.DIALOG_BODY
                + "</html>",
        dialogLinkHtml: "<body class='wym_dialog wym_dialog_link'"
                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
                + ">"
                + "<form>"
                + "<fieldset>"
                + "<input type='hidden' class='wym_dialog_type' value='"
                + WYMeditor.DIALOG_LINK
                + "' />"
                + "<legend>{Link}</legend>"
                + "<div class='row'>"
                + "<label>{URL}</label>"
                + "<input type='text' class='wym_href' value='' size='40' />"
                + "</div>"
                + "<div class='row'>"
                + "<label>{Title}</label>"
                + "<input type='text' class='wym_title' value='' size='40' />"
                + "</div>"
                + "<div class='row row-indent'>"
                + "<input class='wym_submit' type='button'"
                + " value='{Submit}' />"
                + "<input class='wym_cancel' type='button'"
                + "value='{Cancel}' />"
                + "</div>"
                + "</fieldset>"
                + "</form>"
                + "</body>",
        dialogImageHtml: "<body class='wym_dialog wym_dialog_image'"
                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
                + ">"
                + "<form>"
                + "<fieldset>"
                + "<input type='hidden' class='wym_dialog_type' value='"
                + WYMeditor.DIALOG_IMAGE
                + "' />"
                + "<legend>{Image}</legend>"
                + "<div class='row'>"
                + "<label>{URL}</label>"
                + "<input type='text' class='wym_src' value='' size='40' />"
                + "</div>"
                + "<div class='row'>"
                + "<label>{Alternative_Text}</label>"
                + "<input type='text' class='wym_alt' value='' size='40' />"
                + "</div>"
                + "<div class='row'>"
                + "<label>{Title}</label>"
                + "<input type='text' class='wym_title' value='' size='40' />"
                + "</div>"
                + "<div class='row row-indent'>"
                + "<input class='wym_submit' type='button'"
                + " value='{Submit}' />"
                + "<input class='wym_cancel' type='button'"
                + "value='{Cancel}' />"
                + "</div>"
                + "</fieldset>"
                + "</form>"
                + "</body>",
        dialogTableHtml: "<body class='wym_dialog wym_dialog_table'"
                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
                + ">"
                + "<form>"
                + "<fieldset>"
                + "<input type='hidden' class='wym_dialog_type' value='"
                + WYMeditor.DIALOG_TABLE
                + "' />"
                + "<legend>{Table}</legend>"
                + "<div class='row'>"
                + "<label>{Caption}</label>"
                + "<input type='text' class='wym_caption' value='' size='40' />"
                + "</div>"
                + "<div class='row'>"
                + "<label>{Summary}</label>"
                + "<input type='text' class='wym_summary' value='' size='40' />"
                + "</div>"
                + "<div class='row'>"
                + "<label>{Number_Of_Rows}</label>"
                + "<input type='text' class='wym_rows' value='3' size='3' />"
                + "</div>"
                + "<div class='row'>"
                + "<label>{Number_Of_Cols}</label>"
                + "<input type='text' class='wym_cols' value='2' size='3' />"
                + "</div>"
                + "<div class='row row-indent'>"
                + "<input class='wym_submit' type='button'"
                + " value='{Submit}' />"
                + "<input class='wym_cancel' type='button'"
                + "value='{Cancel}' />"
                + "</div>"
                + "</fieldset>"
                + "</form>"
                + "</body>",
        dialogPasteHtml: "<body class='wym_dialog wym_dialog_paste'"
                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
                + ">"
                + "<form>"
                + "<input type='hidden' class='wym_dialog_type' value='"
                + WYMeditor.DIALOG_PASTE
                + "' />"
                + "<fieldset>"
                + "<legend>{Paste_From_Word}</legend>"
                + "<div class='row'>"
                + "<textarea class='wym_text' rows='10' cols='50'></textarea>"
                + "</div>"
                + "<div class='row'>"
                + "<input class='wym_submit' type='button'"
                + " value='{Submit}' />"
                + "<input class='wym_cancel' type='button'"
                + "value='{Cancel}' />"
                + "</div>"
                + "</fieldset>"
                + "</form>"
                + "</body>",
        dialogPreviewHtml: "<body class='wym_dialog wym_dialog_preview'"
                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
                + "></body>",
        dialogStyles: [],
        stringDelimiterLeft: "{",
        stringDelimiterRight: "}",
        preInit: null,
        preBind: null,
        postInit: null,
        preInitDialog: null,
        postInitDialog: null

    }, options);

    return this.each(function() {

        new WYMeditor.editor(jQuery(this), options);
    });
};

/* @name extend
 * @description Returns the WYMeditor instance based on its index
 */
jQuery.extend({
    wymeditors: function(i) {
        return (WYMeditor.INSTANCES[i]);
    }
});


/********** WYMeditor **********/

/* @name Wymeditor
 * @description WYMeditor class
 */

/* @name init
 * @description Initializes a WYMeditor instance
 */
WYMeditor.editor.prototype.init = function() {

    //load subclass - browser specific
    //unsupported browsers: do nothing
    if (jQuery.browser.msie) {
        var WymClass = new WYMeditor.WymClassExplorer(this);
    }
    else if (jQuery.browser.mozilla) {
        var WymClass = new WYMeditor.WymClassMozilla(this);
    }
    else if (jQuery.browser.opera) {
        var WymClass = new WYMeditor.WymClassOpera(this);
    }
    else if (jQuery.browser.safari) {
        var WymClass = new WYMeditor.WymClassSafari(this);
    }

    if (WymClass) {

        if (jQuery.isFunction(this._options.preInit))
            this._options.preInit(this);

        var SaxListener = new WYMeditor.XhtmlSaxListener();
        jQuery.extend(SaxListener, WymClass);
        this.parser = new WYMeditor.XhtmlParser(SaxListener);

        if (this._options.styles || this._options.stylesheet) {
            this.configureEditorUsingRawCss();
        }

        this.helper = new WYMeditor.XmlHelper();

        //extend the Wymeditor object
        //don't use jQuery.extend since 1.1.4
        //jQuery.extend(this, WymClass);
        for (var prop in WymClass) {
            this[prop] = WymClass[prop];
        }

        //load wymbox
        this._box = jQuery(this._element).hide().after(this._options.boxHtml).next().addClass('wym_box_' + this._index);

        //store the instance index in wymbox and element replaced by editor instance
        //but keep it compatible with jQuery < 1.2.3, see #122
        if (jQuery.isFunction(jQuery.fn.data)) {
            jQuery.data(this._box.get(0), WYMeditor.WYM_INDEX, this._index);
            jQuery.data(this._element.get(0), WYMeditor.WYM_INDEX, this._index);
        }

        var h = WYMeditor.Helper;

        //construct the iframe
        var iframeHtml = this._options.iframeHtml;
        iframeHtml = h.replaceAll(iframeHtml, WYMeditor.INDEX, this._index);
        iframeHtml = h.replaceAll(iframeHtml, WYMeditor.IFRAME_BASE_PATH, this._options.iframeBasePath);

        //construct wymbox
        var boxHtml = jQuery(this._box).html();

        boxHtml = h.replaceAll(boxHtml, WYMeditor.LOGO, this._options.logoHtml);
        boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS, this._options.toolsHtml);
        boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS, this._options.containersHtml);
        boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES, this._options.classesHtml);
        boxHtml = h.replaceAll(boxHtml, WYMeditor.HTML, this._options.htmlHtml);
        boxHtml = h.replaceAll(boxHtml, WYMeditor.IFRAME, iframeHtml);
        boxHtml = h.replaceAll(boxHtml, WYMeditor.STATUS, this._options.statusHtml);

        //construct tools list
        var aTools = eval(this._options.toolsItems);
        var sTools = "";

        for (var i = 0; i < aTools.length; i++) {
            var oTool = aTools[i];
            if (oTool.name && oTool.title)
                var sTool = this._options.toolsItemHtml;
            var sTool = h.replaceAll(sTool, WYMeditor.TOOL_NAME, oTool.name);
            sTool = h.replaceAll(sTool, WYMeditor.TOOL_TITLE, this._options.stringDelimiterLeft
                    + oTool.title
                    + this._options.stringDelimiterRight);
            sTool = h.replaceAll(sTool, WYMeditor.TOOL_CLASS, oTool.css);
            sTools += sTool;
        }

        boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS_ITEMS, sTools);

        //construct classes list
        var aClasses = eval(this._options.classesItems);
        var sClasses = "";

        for (var i = 0; i < aClasses.length; i++) {
            var oClass = aClasses[i];
            if (oClass.name && oClass.title)
                var sClass = this._options.classesItemHtml;
            sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name);
            sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title);
            sClasses += sClass;
        }

        boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES_ITEMS, sClasses);

        //construct containers list
        var aContainers = eval(this._options.containersItems);
        var sContainers = "";

        for (var i = 0; i < aContainers.length; i++) {
            var oContainer = aContainers[i];
            if (oContainer.name && oContainer.title)
                var sContainer = this._options.containersItemHtml;
            sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_NAME, oContainer.name);
            sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_TITLE,
                    this._options.stringDelimiterLeft
                    + oContainer.title
                    + this._options.stringDelimiterRight);
            sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_CLASS, oContainer.css);
            sContainers += sContainer;
        }

        boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS_ITEMS, sContainers);

        //l10n
        boxHtml = this.replaceStrings(boxHtml);

        //load html in wymbox
        jQuery(this._box).html(boxHtml);

        //hide the html value
        jQuery(this._box).find(this._options.htmlSelector).hide();

        //enable the skin
        this.loadSkin();

    }
};

WYMeditor.editor.prototype.bindEvents = function() {

    //copy the instance
    var wym = this;

    //handle click event on tools buttons
    jQuery(this._box).find(this._options.toolSelector).click(function() {
        wym._iframe.contentWindow.focus(); //See #154
        wym.exec(jQuery(this).attr(WYMeditor.NAME));
        return(false);
    });

    //handle click event on containers buttons
    jQuery(this._box).find(this._options.containerSelector).click(function() {
        wym.container(jQuery(this).attr(WYMeditor.NAME));
        return(false);
    });

    //handle keyup event on html value: set the editor value
    //handle focus/blur events to check if the element has focus, see #147
    jQuery(this._box).find(this._options.htmlValSelector)
            .keyup(function() {
        jQuery(wym._doc.body).html(jQuery(this).val());
    })
            .focus(function() {
        jQuery(this).toggleClass('hasfocus');
    })
            .blur(function() {
        jQuery(this).toggleClass('hasfocus');
    });

    //handle click event on classes buttons
    jQuery(this._box).find(this._options.classSelector).click(function() {

        var aClasses = eval(wym._options.classesItems);
        var sName = jQuery(this).attr(WYMeditor.NAME);

        var oClass = WYMeditor.Helper.findByName(aClasses, sName);

        if (oClass) {
            var jqexpr = oClass.expr;
            wym.toggleClass(sName, jqexpr);
        }
        wym._iframe.contentWindow.focus(); //See #154
        return(false);
    });

    //handle event on update element
    jQuery(this._options.updateSelector)
            .bind(this._options.updateEvent, function() {
        wym.update();
    });
};

WYMeditor.editor.prototype.ready = function() {
    return(this._doc != null);
};


/********** METHODS **********/

/* @name box
 * @description Returns the WYMeditor container
 */
WYMeditor.editor.prototype.box = function() {
    return(this._box);
};

/* @name html
 * @description Get/Set the html value
 */
WYMeditor.editor.prototype.html = function(html) {

    if (typeof html === 'string')
        jQuery(this._doc.body).html(html);
    else
        return(jQuery(this._doc.body).html());
};

/* @name xhtml
 * @description Cleans up the HTML
 */
WYMeditor.editor.prototype.xhtml = function() {
    return this.parser.parse(this.html());
};

/* @name exec
 * @description Executes a button command
 */
WYMeditor.editor.prototype.exec = function(cmd) {

    //base function for execCommand
    //open a dialog or exec
    switch (cmd) {
        case WYMeditor.CREATE_LINK:
            var container = this.container();
            if (container || this._selected_image)
                this.dialog(WYMeditor.DIALOG_LINK);
            break;

        case WYMeditor.INSERT_IMAGE:
            this.dialog(WYMeditor.DIALOG_IMAGE);
            break;

        case WYMeditor.INSERT_TABLE:
            this.dialog(WYMeditor.DIALOG_TABLE);
            break;

        case WYMeditor.PASTE:
            this.dialog(WYMeditor.DIALOG_PASTE);
            break;

        case WYMeditor.TOGGLE_HTML:
            this.update();
            this.toggleHtml();
            break;

        case WYMeditor.PREVIEW:
            this.dialog(WYMeditor.PREVIEW, this._options.dialogFeaturesPreview);
            break;

        default:
            this._exec(cmd);
            break;
    }
};

/* @name container
 * @description Get/Set the selected container
 */
WYMeditor.editor.prototype.container = function(sType) {

    if (sType) {

        var container = null;

        if (sType.toLowerCase() == WYMeditor.TH) {

            container = this.container();

            //find the TD or TH container
            switch (container.tagName.toLowerCase()) {

                case WYMeditor.TD:
                case WYMeditor.TH:
                    break;
                default:
                    var aTypes = new Array(WYMeditor.TD, WYMeditor.TH);
                    container = this.findUp(this.container(), aTypes);
                    break;
            }

            //if it exists, switch
            if (container != null) {

                sType = (container.tagName.toLowerCase() == WYMeditor.TD) ? WYMeditor.TH : WYMeditor.TD;
                this.switchTo(container, sType);
                this.update();
            }
        } else {

            //set the container type
            var aTypes = new Array(WYMeditor.P, WYMeditor.H1, WYMeditor.H2, WYMeditor.H3, WYMeditor.H4, WYMeditor.H5,
                    WYMeditor.H6, WYMeditor.PRE, WYMeditor.BLOCKQUOTE);
            container = this.findUp(this.container(), aTypes);

            if (container) {

                var newNode = null;

                //blockquotes must contain a block level element
                if (sType.toLowerCase() == WYMeditor.BLOCKQUOTE) {

                    var blockquote = this.findUp(this.container(), WYMeditor.BLOCKQUOTE);

                    if (blockquote == null) {

                        newNode = this._doc.createElement(sType);
                        container.parentNode.insertBefore(newNode, container);
                        newNode.appendChild(container);
                        this.setFocusToNode(newNode.firstChild);

                    } else {

                        var nodes = blockquote.childNodes;
                        var lgt = nodes.length;
                        var firstNode = null;

                        if (lgt > 0)
                            firstNode = nodes.item(0);
                        for (var x = 0; x < lgt; x++) {
                            blockquote.parentNode.insertBefore(nodes.item(0), blockquote);
                        }
                        blockquote.parentNode.removeChild(blockquote);
                        if (firstNode)
                            this.setFocusToNode(firstNode);
                    }
                }

                else
                    this.switchTo(container, sType);

                this.update();
            }
        }
    }
    else
        return(this.selected());
};

/* @name toggleClass
 * @description Toggles class on selected element, or one of its parents
 */
WYMeditor.editor.prototype.toggleClass = function(sClass, jqexpr) {

    var container = (this._selected_image
            ? this._selected_image
            : jQuery(this.selected()));
    container = jQuery(container).parentsOrSelf(jqexpr);
    jQuery(container).toggleClass(sClass);

    if (!jQuery(container).attr(WYMeditor.CLASS))
        jQuery(container).removeAttr(this._class);

};

/* @name findUp
 * @description Returns the first parent or self container, based on its type
 */
WYMeditor.editor.prototype.findUp = function(node, filter) {

    //filter is a string or an array of strings

    if (node) {

        var tagname = node.tagName.toLowerCase();

        if (typeof(filter) == WYMeditor.STRING) {

            while (tagname != filter && tagname != WYMeditor.BODY) {

                node = node.parentNode;
                tagname = node.tagName.toLowerCase();
            }

        } else {

            var bFound = false;

            while (!bFound && tagname != WYMeditor.BODY) {
                for (var i = 0; i < filter.length; i++) {
                    if (tagname == filter[i]) {
                        bFound = true;
                        break;
                    }
                }
                if (!bFound) {
                    node = node.parentNode;
                    tagname = node.tagName.toLowerCase();
                }
            }
        }

        if (tagname != WYMeditor.BODY)
            return(node);
        else
            return(null);

    } else
        return(null);
};

/* @name switchTo
 * @description Switch the node's type
 */
WYMeditor.editor.prototype.switchTo = function(node, sType) {

    var newNode = this._doc.createElement(sType);
    var html = jQuery(node).html();
    node.parentNode.replaceChild(newNode, node);
    jQuery(newNode).html(html);
    this.setFocusToNode(newNode);
};

WYMeditor.editor.prototype.replaceStrings = function(sVal) {
    //check if the language file has already been loaded
    //if not, get it via a synchronous ajax call
    if (!WYMeditor.STRINGS[this._options.lang]) {
        try {
            eval(jQuery.ajax({url: this._options.langPath
                        + this._options.lang + '.js', async: false}).responseText);
        } catch (e) {
            WYMeditor.console.error("WYMeditor: error while parsing language file.");
            return sVal;
        }
    }

    //replace all the strings in sVal and return it
    for (var key in WYMeditor.STRINGS[this._options.lang]) {
        sVal = WYMeditor.Helper.replaceAll(sVal, this._options.stringDelimiterLeft + key
                + this._options.stringDelimiterRight,
                WYMeditor.STRINGS[this._options.lang][key]);
    }
    ;
    return(sVal);
};

WYMeditor.editor.prototype.encloseString = function(sVal) {

    return(this._options.stringDelimiterLeft
            + sVal
            + this._options.stringDelimiterRight);
};

/* @name status
 * @description Prints a status message
 */
WYMeditor.editor.prototype.status = function(sMessage) {

    //print status message
    jQuery(this._box).find(this._options.statusSelector).html(sMessage);
};

/* @name update
 * @description Updates the element and textarea values
 */
WYMeditor.editor.prototype.update = function() {

    var html = this.xhtml();
    jQuery(this._element).val(html);
    jQuery(this._box).find(this._options.htmlValSelector).not('.hasfocus').val(html); //#147
};

/* @name dialog
 * @description Opens a dialog box
 */
WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtml) {

    var features = dialogFeatures || this._wym._options.dialogFeatures;
    var wDialog = window.open('', 'dialog', features);

    if (wDialog) {

        var sBodyHtml = "";

        switch (dialogType) {

            case(WYMeditor.DIALOG_LINK):
                sBodyHtml = this._options.dialogLinkHtml;
                break;
            case(WYMeditor.DIALOG_IMAGE):
                sBodyHtml = this._options.dialogImageHtml;
                break;
            case(WYMeditor.DIALOG_TABLE):
                sBodyHtml = this._options.dialogTableHtml;
                break;
            case(WYMeditor.DIALOG_PASTE):
                sBodyHtml = this._options.dialogPasteHtml;
                break;
            case(WYMeditor.PREVIEW):
                sBodyHtml = this._options.dialogPreviewHtml;
                break;

            default:
                sBodyHtml = bodyHtml;
        }

        var h = WYMeditor.Helper;

        //construct the dialog
        var dialogHtml = this._options.dialogHtml;
        dialogHtml = h.replaceAll(dialogHtml, WYMeditor.BASE_PATH, this._options.basePath);
        dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIRECTION, this._options.direction);
        dialogHtml = h.replaceAll(dialogHtml, WYMeditor.CSS_PATH, this._options.skinPath + WYMeditor.SKINS_DEFAULT_CSS);
        dialogHtml = h.replaceAll(dialogHtml, WYMeditor.WYM_PATH, this._options.wymPath);
        dialogHtml = h.replaceAll(dialogHtml, WYMeditor.JQUERY_PATH, this._options.jQueryPath);
        dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIALOG_TITLE, this.encloseString(dialogType));
        dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIALOG_BODY, sBodyHtml);
        dialogHtml = h.replaceAll(dialogHtml, WYMeditor.INDEX, this._index);

        dialogHtml = this.replaceStrings(dialogHtml);

        var doc = wDialog.document;
        doc.write(dialogHtml);
        doc.close();
    }
};

/* @name toggleHtml
 * @description Shows/Hides the HTML
 */
WYMeditor.editor.prototype.toggleHtml = function() {
    jQuery(this._box).find(this._options.htmlSelector).toggle();
};

WYMeditor.editor.prototype.uniqueStamp = function() {
    var now = new Date();
    return("wym-" + now.getTime());
};

WYMeditor.editor.prototype.paste = function(sData) {

    var sTmp;
    var container = this.selected();

    //split the data, using double newlines as the separator
    var aP = sData.split(this._newLine + this._newLine);
    var rExp = new RegExp(this._newLine, "g");

    //add a P for each item
    if (container && container.tagName.toLowerCase() != WYMeditor.BODY) {
        for (x = aP.length - 1; x >= 0; x--) {
            sTmp = aP[x];
            //simple newlines are replaced by a break
            sTmp = sTmp.replace(rExp, "<br />");
            jQuery(container).after("<p>" + sTmp + "</p>");
        }
    } else {
        for (x = 0; x < aP.length; x++) {
            sTmp = aP[x];
            //simple newlines are replaced by a break
            sTmp = sTmp.replace(rExp, "<br />");
            jQuery(this._doc.body).append("<p>" + sTmp + "</p>");
        }

    }
};

WYMeditor.editor.prototype.insert = function(html) {
    // Do we have a selection?
    if (this._iframe.contentWindow.getSelection().focusNode != null) {
        // Overwrite selection with provided html
        this._exec(WYMeditor.INSERT_HTML, html);
    } else {
        // Fall back to the internal paste function if there's no selection
        this.paste(html)
    }
};

WYMeditor.editor.prototype.wrap = function(left, right) {
    // Do we have a selection?
    if (this._iframe.contentWindow.getSelection().focusNode != null) {
        // Wrap selection with provided html
        this._exec(WYMeditor.INSERT_HTML, left + this._iframe.contentWindow.getSelection().toString() + right);
    }
};

WYMeditor.editor.prototype.unwrap = function() {
    // Do we have a selection?
    if (this._iframe.contentWindow.getSelection().focusNode != null) {
        // Unwrap selection
        this._exec(WYMeditor.INSERT_HTML, this._iframe.contentWindow.getSelection().toString());
    }
};

WYMeditor.editor.prototype.setFocusToNode = function(node, toStart) {
    var range = this._doc.createRange(),
            selection = this._iframe.contentWindow.getSelection();
    toStart = toStart ? 0 : 1;

    range.selectNodeContents(node);
    selection.addRange(range);
    selection.collapse(node, toStart);
    this._iframe.contentWindow.focus();
};

WYMeditor.editor.prototype.addCssRules = function(doc, aCss) {
    var styles = doc.styleSheets[0];
    if (styles) {
        for (var i = 0; i < aCss.length; i++) {
            var oCss = aCss[i];
            if (oCss.name && oCss.css)
                this.addCssRule(styles, oCss);
        }
    }
};

/********** CONFIGURATION **********/

WYMeditor.editor.prototype.computeBasePath = function() {
    return jQuery(jQuery.grep(jQuery('script'), function(s) {
        return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/))
    })).attr('src').replace(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/, '');
};

WYMeditor.editor.prototype.computeWymPath = function() {
    return jQuery(jQuery.grep(jQuery('script'), function(s) {
        return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/))
    })).attr('src');
};

WYMeditor.editor.prototype.computeJqueryPath = function() {
    return jQuery(jQuery.grep(jQuery('script'), function(s) {
        return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/))
    })).attr('src');
};

WYMeditor.editor.prototype.computeCssPath = function() {
    return jQuery(jQuery.grep(jQuery('link'), function(s) {
        return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/))
    })).attr('href');
};

WYMeditor.editor.prototype.configureEditorUsingRawCss = function() {

    var CssParser = new WYMeditor.WymCssParser();
    if (this._options.stylesheet) {
        CssParser.parse(jQuery.ajax({url: this._options.stylesheet, async: false}).responseText);
    } else {
        CssParser.parse(this._options.styles, false);
    }

    if (this._options.classesItems.length == 0) {
        this._options.classesItems = CssParser.css_settings.classesItems;
    }
    if (this._options.editorStyles.length == 0) {
        this._options.editorStyles = CssParser.css_settings.editorStyles;
    }
    if (this._options.dialogStyles.length == 0) {
        this._options.dialogStyles = CssParser.css_settings.dialogStyles;
    }
};

/********** EVENTS **********/

WYMeditor.editor.prototype.listen = function() {
    //don't use jQuery.find() on the iframe body
    //because of MSIE + jQuery + expando issue (#JQ1143)
    //jQuery(this._doc.body).find("*").bind("mouseup", this.mouseup);

    jQuery(this._doc.body).bind("mousedown", this.mousedown);
};

WYMeditor.editor.prototype.mousedown = function(evt) {
    var wym = WYMeditor.INSTANCES[this.ownerDocument.title];
    wym._selected_image = (evt.target.tagName.toLowerCase() == WYMeditor.IMG) ? evt.target : null;
};

/********** SKINS **********/

/*
 * Function: WYMeditor.loadCss
 *      Loads a stylesheet in the document.
 *
 * Parameters:
 *      href - The CSS path.
 */
WYMeditor.loadCss = function(href) {

    var link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;

    var head = jQuery('head').get(0);
    head.appendChild(link);
};

/*
 *  Function: WYMeditor.editor.loadSkin
 *      Loads the skin CSS and initialization script (if needed).
 */
WYMeditor.editor.prototype.loadSkin = function() {

    //does the user want to automatically load the CSS (default: yes)?
    //we also test if it hasn't been already loaded by another instance
    //see below for a better (second) test
    if (this._options.loadSkin && !WYMeditor.SKINS[this._options.skin]) {

        //check if it hasn't been already loaded
        //so we don't load it more than once
        //(we check the existing <link> elements)

        var found = false;
        var rExp = new RegExp(this._options.skin
                + '\/' + WYMeditor.SKINS_DEFAULT_CSS + '$');

        jQuery('link').each(function() {
            if (this.href.match(rExp))
                found = true;
        });

        //load it, using the skin path
        if (!found)
            WYMeditor.loadCss(this._options.skinPath
                    + WYMeditor.SKINS_DEFAULT_CSS);
    }

    //put the classname (ex. wym_skin_default) on wym_box
    jQuery(this._box).addClass("wym_skin_" + this._options.skin);

    //does the user want to use some JS to initialize the skin (default: yes)?
    //also check if it hasn't already been loaded by another instance
    if (this._options.initSkin && !WYMeditor.SKINS[this._options.skin]) {

        eval(jQuery.ajax({url: this._options.skinPath
                    + WYMeditor.SKINS_DEFAULT_JS, async: false}).responseText);
    }

    //init the skin, if needed
    if (WYMeditor.SKINS[this._options.skin]
            && WYMeditor.SKINS[this._options.skin].init)
        WYMeditor.SKINS[this._options.skin].init(this);

};


/********** DIALOGS **********/

WYMeditor.INIT_DIALOG = function(index) {

    var wym = window.opener.WYMeditor.INSTANCES[index];
    var doc = window.document;
    var selected = wym.selected();
    var dialogType = jQuery(wym._options.dialogTypeSelector).val();
    var sStamp = wym.uniqueStamp();

    switch (dialogType) {

        case WYMeditor.DIALOG_LINK:
            //ensure that we select the link to populate the fields
            if (selected && selected.tagName && selected.tagName.toLowerCase != WYMeditor.A)
                selected = jQuery(selected).parentsOrSelf(WYMeditor.A);

            //fix MSIE selection if link image has been clicked
            if (!selected && wym._selected_image)
                selected = jQuery(wym._selected_image).parentsOrSelf(WYMeditor.A);
            break;

    }

    //pre-init functions
    if (jQuery.isFunction(wym._options.preInitDialog))
        wym._options.preInitDialog(wym, window);

    //add css rules from options
    var styles = doc.styleSheets[0];
    var aCss = eval(wym._options.dialogStyles);

    wym.addCssRules(doc, aCss);

    //auto populate fields if selected container (e.g. A)
    if (selected) {
        jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYMeditor.HREF));
        jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC));
        jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE));
        jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.ALT));
    }

    //auto populate image fields if selected image
    if (wym._selected_image) {
        jQuery(wym._options.dialogImageSelector + " " + wym._options.srcSelector)
                .val(jQuery(wym._selected_image).attr(WYMeditor.SRC));
        jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector)
                .val(jQuery(wym._selected_image).attr(WYMeditor.TITLE));
        jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector)
                .val(jQuery(wym._selected_image).attr(WYMeditor.ALT));
    }

    jQuery(wym._options.dialogLinkSelector + " "
            + wym._options.submitSelector).click(function() {

        var sUrl = jQuery(wym._options.hrefSelector).val();
        if (sUrl.length > 0) {
            var link;

            if (selected[0] && selected[0].tagName.toLowerCase() == WYMeditor.A) {
                link = selected;
            } else {
                wym._exec(WYMeditor.CREATE_LINK, sStamp);
                link = jQuery("a[href=" + sStamp + "]", wym._doc.body);
            }

            link.attr(WYMeditor.HREF, sUrl)
                    .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val());

        }
        window.close();
    });

    jQuery(wym._options.dialogImageSelector + " "
            + wym._options.submitSelector).click(function() {

        var sUrl = jQuery(wym._options.srcSelector).val();
        if (sUrl.length > 0) {

            wym._exec(WYMeditor.INSERT_IMAGE, sStamp);

            jQuery("img[src$=" + sStamp + "]", wym._doc.body)
                    .attr(WYMeditor.SRC, sUrl)
                    .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val())
                    .attr(WYMeditor.ALT, jQuery(wym._options.altSelector).val());
        }
        window.close();
    });

    jQuery(wym._options.dialogTableSelector + " "
            + wym._options.submitSelector).click(function() {

        var iRows = jQuery(wym._options.rowsSelector).val();
        var iCols = jQuery(wym._options.colsSelector).val();

        if (iRows > 0 && iCols > 0) {

            var table = wym._doc.createElement(WYMeditor.TABLE);
            var newRow = null;
            var newCol = null;

            var sCaption = jQuery(wym._options.captionSelector).val();

            //we create the caption
            var newCaption = table.createCaption();
            newCaption.innerHTML = sCaption;

            //we create the rows and cells
            for (x = 0; x < iRows; x++) {
                newRow = table.insertRow(x);
                for (y = 0; y < iCols; y++) {
                    newRow.insertCell(y);
                }
            }

            //set the summary attr
            jQuery(table).attr('summary',
                    jQuery(wym._options.summarySelector).val());

            //append the table after the selected container
            var node = jQuery(wym.findUp(wym.container(),
                    WYMeditor.MAIN_CONTAINERS)).get(0);
            if (!node || !node.parentNode)
                jQuery(wym._doc.body).append(table);
            else
                jQuery(node).after(table);
        }
        window.close();
    });

    jQuery(wym._options.dialogPasteSelector + " "
            + wym._options.submitSelector).click(function() {

        var sText = jQuery(wym._options.textSelector).val();
        wym.paste(sText);
        window.close();
    });

    jQuery(wym._options.dialogPreviewSelector + " "
            + wym._options.previewSelector)
            .html(wym.xhtml());

    //cancel button
    jQuery(wym._options.cancelSelector).mousedown(function() {
        window.close();
    });

    //pre-init functions
    if (jQuery.isFunction(wym._options.postInitDialog))
        wym._options.postInitDialog(wym, window);

};

/********** XHTML LEXER/PARSER **********/

/*
 * @name xml
 * @description Use these methods to generate XML and XHTML compliant tags and
 * escape tag attributes correctly
 * @author Bermi Ferrer - http://bermi.org
 * @author David Heinemeier Hansson http://loudthinking.com
 */
WYMeditor.XmlHelper = function()
{
    this._entitiesDiv = document.createElement('div');
    return this;
};


/*
 * @name tag
 * @description
 * Returns an empty HTML tag of type *name* which by default is XHTML
 * compliant. Setting *open* to true will create an open tag compatible
 * with HTML 4.0 and below. Add HTML attributes by passing an attributes
 * array to *options*. For attributes with no value like (disabled and
 * readonly), give it a value of true in the *options* array.
 *
 * Examples:
 *
 *   this.tag('br')
 *    # => <br />
 *   this.tag ('br', false, true)
 *    # => <br>
 *   this.tag ('input', jQuery({type:'text',disabled:true }) )
 *    # => <input type="text" disabled="disabled" />
 */
WYMeditor.XmlHelper.prototype.tag = function(name, options, open)
{
    options = options || false;
    open = open || false;
    return '<' + name + (options ? this.tagOptions(options) : '') + (open ? '>' : ' />');
};

/*
 * @name contentTag
 * @description
 * Returns a XML block tag of type *name* surrounding the *content*. Add
 * XML attributes by passing an attributes array to *options*. For attributes
 * with no value like (disabled and readonly), give it a value of true in
 * the *options* array. You can use symbols or strings for the attribute names.
 *
 *   this.contentTag ('p', 'Hello world!' )
 *    # => <p>Hello world!</p>
 *   this.contentTag('div', this.contentTag('p', "Hello world!"), jQuery({class : "strong"}))
 *    # => <div class="strong"><p>Hello world!</p></div>
 *   this.contentTag("select", options, jQuery({multiple : true}))
 *    # => <select multiple="multiple">...options...</select>
 */
WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options)
{
    options = options || false;
    return '<' + name + (options ? this.tagOptions(options) : '') + '>' + content + '</' + name + '>';
};

/*
 * @name cdataSection
 * @description
 * Returns a CDATA section for the given +content+.  CDATA sections
 * are used to escape blocks of text containing characters which would
 * otherwise be recognized as markup. CDATA sections begin with the string
 * <tt>&lt;![CDATA[</tt> and } with (and may not contain) the string
 * <tt>]]></tt>.
 */
WYMeditor.XmlHelper.prototype.cdataSection = function(content)
{
    return '<![CDATA[' + content + ']]>';
};


/*
 * @name escapeOnce
 * @description
 * Returns the escaped +xml+ without affecting existing escaped entities.
 *
 *  this.escapeOnce( "1 > 2 &amp; 3")
 *    # => "1 &gt; 2 &amp; 3"
 */
WYMeditor.XmlHelper.prototype.escapeOnce = function(xml)
{
    return this._fixDoubleEscape(this.escapeEntities(xml));
};

/*
 * @name _fixDoubleEscape
 * @description
 * Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc.
 */
WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped)
{
    return escaped.replace(/&amp;([a-z]+|(#\d+));/ig, "&$1;");
};

/*
 * @name tagOptions
 * @description
 * Takes an array like the one generated by Tag.parseAttributes
 *  [["src", "http://www.editam.com/?a=b&c=d&amp;f=g"], ["title", "Editam, <Simplified> CMS"]]
 * or an object like {src:"http://www.editam.com/?a=b&c=d&amp;f=g", title:"Editam, <Simplified> CMS"}
 * and returns a string properly escaped like
 * ' src = "http://www.editam.com/?a=b&amp;c=d&amp;f=g" title = "Editam, &lt;Simplified&gt; CMS"'
 * which is valid for strict XHTML
 */
WYMeditor.XmlHelper.prototype.tagOptions = function(options)
{
    var xml = this;
    xml._formated_options = '';

    for (var key in options) {
        var formated_options = '';
        var value = options[key];
        if (typeof value != 'function' && value.length > 0) {

            if (parseInt(key) == key && typeof value == 'object') {
                key = value.shift();
                value = value.pop();
            }
            if (key != '' && value != '') {
                xml._formated_options += ' ' + key + '="' + xml.escapeOnce(value) + '"';
            }
        }
    }
    return xml._formated_options;
};

/*
 * @name escapeEntities
 * @description
 * Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it
 * will not escape ". If set to true it will also escape '
 */
WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes)
{
    this._entitiesDiv.innerHTML = string;
    this._entitiesDiv.textContent = string;
    var result = this._entitiesDiv.innerHTML;
    if (typeof escape_quotes == 'undefined') {
        if (escape_quotes != false)
            result = result.replace('"', '&quot;');
        if (escape_quotes == true)
            result = result.replace('"', '&#039;');
    }
    return result;
};

/*
 * Parses a string conatining tag attributes and values an returns an array formated like
 *  [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]]
 */
WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes)
{
    // Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs
    var result = [];
    var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g);
    if (matches.toString() != tag_attributes) {
        for (var k in matches) {
            var v = matches[k];
            if (typeof v != 'function' && v.length != 0) {
                var re = new RegExp('(\\w+)\\s*' + v);
                if (match = tag_attributes.match(re)) {
                    var value = v.replace(/^[\s=]+/, "");
                    var delimiter = value.charAt(0);
                    delimiter = delimiter == '"' ? '"' : (delimiter == "'" ? "'" : '');
                    if (delimiter != '') {
                        value = delimiter == '"' ? value.replace(/^"|"+$/g, '') : value.replace(/^'|'+$/g, '');
                    }
                    tag_attributes = tag_attributes.replace(match[0], '');
                    result.push([match[1], value]);
                }
            }
        }
    }
    return result;
};

/**
 * XhtmlValidator for validating tag attributes
 *
 * @author Bermi Ferrer - http://bermi.org
 */
WYMeditor.XhtmlValidator = {
    "_attributes":
            {
                "core":
                        {
                            "except": [
                                "base",
                                "head",
                                "html",
                                "meta",
                                "param",
                                "script",
                                "style",
                                "title"
                            ],
                            "attributes": [
                                "class",
                                "id",
                                "style",
                                "title",
                                "accesskey",
                                "tabindex"
                            ]
                        },
                "language":
                        {
                            "except": [
                                "base",
                                "br",
                                "hr",
                                "iframe",
                                "param",
                                "script"
                            ],
                            "attributes":
                                    {
                                        "dir": [
                                            "ltr",
                                            "rtl"
                                        ],
                                        "0": "lang",
                                        "1": "xml:lang"
                                    }
                        },
                "keyboard":
                        {
                            "attributes":
                                    {
                                        "accesskey": /^(\w){1}$/,
                                        "tabindex": /^(\d)+$/
                                    }
                        }
            },
    "_events":
            {
                "window":
                        {
                            "only": [
                                "body"
                            ],
                            "attributes": [
                                "onload",
                                "onunload"
                            ]
                        },
                "form":
                        {
                            "only": [
                                "form",
                                "input",
                                "textarea",
                                "select",
                                "a",
                                "label",
                                "button"
                            ],
                            "attributes": [
                                "onchange",
                                "onsubmit",
                                "onreset",
                                "onselect",
                                "onblur",
                                "onfocus"
                            ]
                        },
                "keyboard":
                        {
                            "except": [
                                "base",
                                "bdo",
                                "br",
                                "frame",
                                "frameset",
                                "head",
                                "html",
                                "iframe",
                                "meta",
                                "param",
                                "script",
                                "style",
                                "title"
                            ],
                            "attributes": [
                                "onkeydown",
                                "onkeypress",
                                "onkeyup"
                            ]
                        },
                "mouse":
                        {
                            "except": [
                                "base",
                                "bdo",
                                "br",
                                "head",
                                "html",
                                "meta",
                                "param",
                                "script",
                                "style",
                                "title"
                            ],
                            "attributes": [
                                "onclick",
                                "ondblclick",
                                "onmousedown",
                                "onmousemove",
                                "onmouseover",
                                "onmouseout",
                                "onmouseup"
                            ]
                        }
            },
    "_tags":
            {
                "a":
                        {
                            "attributes":
                                    {
                                        "0": "charset",
                                        "1": "coords",
                                        "2": "href",
                                        "3": "hreflang",
                                        "4": "name",
                                        "rel": /^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/,
                                        "rev": /^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/,
                                        "shape": /^(rect|rectangle|circ|circle|poly|polygon)$/,
                                        "5": "type"
                                    }
                        },
                "0": "abbr",
                "1": "acronym",
                "2": "address",
                "area":
                        {
                            "attributes":
                                    {
                                        "0": "alt",
                                        "1": "coords",
                                        "2": "href",
                                        "nohref": /^(true|false)$/,
                                        "shape": /^(rect|rectangle|circ|circle|poly|polygon)$/
                                    },
                            "required": [
                                "alt"
                            ]
                        },
                "3": "b",
                "base":
                        {
                            "attributes": [
                                "href"
                            ],
                            "required": [
                                "href"
                            ]
                        },
                "bdo":
                        {
                            "attributes":
                                    {
                                        "dir": /^(ltr|rtl)$/
                                    },
                            "required": [
                                "dir"
                            ]
                        },
                "4": "big",
                "blockquote":
                        {
                            "attributes": [
                                "cite"
                            ]
                        },
                "5": "body",
                "6": "br",
                "button":
                        {
                            "attributes":
                                    {
                                        "disabled": /^(disabled)$/,
                                        "type": /^(button|reset|submit)$/,
                                        "0": "value"
                                    },
                            "inside": "form"
                        },
                "7": "caption",
                "8": "cite",
                "9": "code",
                "col":
                        {
                            "attributes":
                                    {
                                        "align": /^(right|left|center|justify)$/,
                                        "0": "char",
                                        "1": "charoff",
                                        "span": /^(\d)+$/,
                                        "valign": /^(top|middle|bottom|baseline)$/,
                                        "2": "width"
                                    },
                            "inside": "colgroup"
                        },
                "colgroup":
                        {
                            "attributes":
                                    {
                                        "align": /^(right|left|center|justify)$/,
                                        "0": "char",
                                        "1": "charoff",
                                        "span": /^(\d)+$/,
                                        "valign": /^(top|middle|bottom|baseline)$/,
                                        "2": "width"
                                    }
                        },
                "10": "dd",
                "del":
                        {
                            "attributes":
                                    {
                                        "0": "cite",
                                        "datetime": /^([0-9]){8}/
                                    }
                        },
                "11": "div",
                "12": "dfn",
                "13": "dl",
                "14": "dt",
                "15": "em",
                "fieldset":
                        {
                            "inside": "form"
                        },
                "form":
                        {
                            "attributes":
                                    {
                                        "0": "action",
                                        "1": "accept",
                                        "2": "accept-charset",
                                        "3": "enctype",
                                        "method": /^(get|post)$/
                                    },
                            "required": [
                                "action"
                            ]
                        },
                "head":
                        {
                            "attributes": [
                                "profile"
                            ]
                        },
                "16": "h1",
                "17": "h2",
                "18": "h3",
                "19": "h4",
                "20": "h5",
                "21": "h6",
                "22": "hr",
                "html":
                        {
                            "attributes": [
                                "xmlns"
                            ]
                        },
                "23": "i",
                "img":
                        {
                            "attributes": [
                                "alt",
                                "src",
                                "height",
                                "ismap",
                                "longdesc",
                                "usemap",
                                "width"
                            ],
                            "required": [
                                "alt",
                                "src"
                            ]
                        },
                "input":
                        {
                            "attributes":
                                    {
                                        "0": "accept",
                                        "1": "alt",
                                        "checked": /^(checked)$/,
                                        "disabled": /^(disabled)$/,
                                        "maxlength": /^(\d)+$/,
                                        "2": "name",
                                        "readonly": /^(readonly)$/,
                                        "size": /^(\d)+$/,
                                        "3": "src",
                                        "type": /^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/,
                                        "4": "value"
                                    },
                            "inside": "form"
                        },
                "ins":
                        {
                            "attributes":
                                    {
                                        "0": "cite",
                                        "datetime": /^([0-9]){8}/
                                    }
                        },
                "24": "kbd",
                "label":
                        {
                            "attributes": [
                                "for"
                            ],
                            "inside": "form"
                        },
                "25": "legend",
                "26": "li",
                "link":
                        {
                            "attributes":
                                    {
                                        "0": "charset",
                                        "1": "href",
                                        "2": "hreflang",
                                        "media": /^(all|braille|print|projection|screen|speech|,|;| )+$/i,
                                        //next comment line required by Opera!
                                        /*"rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,*/
                                        "rel": /^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,
                                        "rev": /^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,
                                        "3": "type"
                                    },
                            "inside": "head"
                        },
                "map":
                        {
                            "attributes": [
                                "id",
                                "name"
                            ],
                            "required": [
                                "id"
                            ]
                        },
                "meta":
                        {
                            "attributes":
                                    {
                                        "0": "content",
                                        "http-equiv": /^(content\-type|expires|refresh|set\-cookie)$/i,
                                        "1": "name",
                                        "2": "scheme"
                                    },
                            "required": [
                                "content"
                            ]
                        },
                "27": "noscript",
                "object":
                        {
                            "attributes": [
                                "archive",
                                "classid",
                                "codebase",
                                "codetype",
                                "data",
                                "declare",
                                "height",
                                "name",
                                "standby",
                                "type",
                                "usemap",
                                "width"
                            ]
                        },
                "28": "ol",
                "optgroup":
                        {
                            "attributes":
                                    {
                                        "0": "label",
                                        "disabled": /^(disabled)$/
                                    },
                            "required": [
                                "label"
                            ]
                        },
                "option":
                        {
                            "attributes":
                                    {
                                        "0": "label",
                                        "disabled": /^(disabled)$/,
                                        "selected": /^(selected)$/,
                                        "1": "value"
                                    },
                            "inside": "select"
                        },
                "29": "p",
                "param":
                        {
                            "attributes":
                                    {
                                        "0": "type",
                                        "valuetype": /^(data|ref|object)$/,
                                        "1": "valuetype",
                                        "2": "value"
                                    },
                            "required": [
                                "name"
                            ]
                        },
                "30": "pre",
                "q":
                        {
                            "attributes": [
                                "cite"
                            ]
                        },
                "31": "samp",
                "script":
                        {
                            "attributes":
                                    {
                                        "type": /^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/,
                                        "0": "charset",
                                        "defer": /^(defer)$/,
                                        "1": "src"
                                    },
                            "required": [
                                "type"
                            ]
                        },
                "select":
                        {
                            "attributes":
                                    {
                                        "disabled": /^(disabled)$/,
                                        "multiple": /^(multiple)$/,
                                        "0": "name",
                                        "1": "size"
                                    },
                            "inside": "form"
                        },
                "32": "small",
                "33": "span",
                "34": "strong",
                "style":
                        {
                            "attributes":
                                    {
                                        "0": "type",
                                        "media": /^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/
                                    },
                            "required": [
                                "type"
                            ]
                        },
                "35": "sub",
                "36": "sup",
                "table":
                        {
                            "attributes":
                                    {
                                        "0": "border",
                                        "1": "cellpadding",
                                        "2": "cellspacing",
                                        "frame": /^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/,
                                        "rules": /^(none|groups|rows|cols|all)$/,
                                        "3": "summary",
                                        "4": "width"
                                    }
                        },
                "tbody":
                        {
                            "attributes":
                                    {
                                        "align": /^(right|left|center|justify)$/,
                                        "0": "char",
                                        "1": "charoff",
                                        "valign": /^(top|middle|bottom|baseline)$/
                                    }
                        },
                "td":
                        {
                            "attributes":
                                    {
                                        "0": "abbr",
                                        "align": /^(left|right|center|justify|char)$/,
                                        "1": "axis",
                                        "2": "char",
                                        "3": "charoff",
                                        "colspan": /^(\d)+$/,
                                        "4": "headers",
                                        "rowspan": /^(\d)+$/,
                                        "scope": /^(col|colgroup|row|rowgroup)$/,
                                        "valign": /^(top|middle|bottom|baseline)$/
                                    }
                        },
                "textarea":
                        {
                            "attributes": [
                                "cols",
                                "rows",
                                "disabled",
                                "name",
                                "readonly"
                            ],
                            "required": [
                                "cols",
                                "rows"
                            ],
                            "inside": "form"
                        },
                "tfoot":
                        {
                            "attributes":
                                    {
                                        "align": /^(right|left|center|justify)$/,
                                        "0": "char",
                                        "1": "charoff",
                                        "valign": /^(top|middle|bottom)$/,
                                        "2": "baseline"
                                    }
                        },
                "th":
                        {
                            "attributes":
                                    {
                                        "0": "abbr",
                                        "align": /^(left|right|center|justify|char)$/,
                                        "1": "axis",
                                        "2": "char",
                                        "3": "charoff",
                                        "colspan": /^(\d)+$/,
                                        "4": "headers",
                                        "rowspan": /^(\d)+$/,
                                        "scope": /^(col|colgroup|row|rowgroup)$/,
                                        "valign": /^(top|middle|bottom|baseline)$/
                                    }
                        },
                "thead":
                        {
                            "attributes":
                                    {
                                        "align": /^(right|left|center|justify)$/,
                                        "0": "char",
                                        "1": "charoff",
                                        "valign": /^(top|middle|bottom|baseline)$/
                                    }
                        },
                "37": "title",
                "tr":
                        {
                            "attributes":
                                    {
                                        "align": /^(right|left|center|justify|char)$/,
                                        "0": "char",
                                        "1": "charoff",
                                        "valign": /^(top|middle|bottom|baseline)$/
                                    }
                        },
                "38": "tt",
                "39": "ul",
                "40": "var"
            },
    // Temporary skiped attributes
    skiped_attributes: [],
    skiped_attribute_values: [],
    getValidTagAttributes: function(tag, attributes)
    {
        var valid_attributes = {};
        var possible_attributes = this.getPossibleTagAttributes(tag);
        for (var attribute in attributes) {
            var value = attributes[attribute];
            var h = WYMeditor.Helper;
            if (!h.contains(this.skiped_attributes, attribute) && !h.contains(this.skiped_attribute_values, value)) {
                if (typeof value != 'function' && h.contains(possible_attributes, attribute)) {
                    if (this.doesAttributeNeedsValidation(tag, attribute)) {
                        if (this.validateAttribute(tag, attribute, value)) {
                            valid_attributes[attribute] = value;
                        }
                    } else {
                        valid_attributes[attribute] = value;
                    }
                }
            }
        }
        return valid_attributes;
    },
    getUniqueAttributesAndEventsForTag: function(tag)
    {
        var result = [];

        if (this._tags[tag] && this._tags[tag]['attributes']) {
            for (k in this._tags[tag]['attributes']) {
                result.push(parseInt(k) == k ? this._tags[tag]['attributes'][k] : k);
            }
        }
        return result;
    },
    getDefaultAttributesAndEventsForTags: function()
    {
        var result = [];
        for (var key in this._events) {
            result.push(this._events[key]);
        }
        for (var key in this._attributes) {
            result.push(this._attributes[key]);
        }
        return result;
    },
    isValidTag: function(tag)
    {
        if (this._tags[tag]) {
            return true;
        }
        for (var key in this._tags) {
            if (this._tags[key] == tag) {
                return true;
            }
        }
        return false;
    },
    getDefaultAttributesAndEventsForTag: function(tag)
    {
        var default_attributes = [];
        if (this.isValidTag(tag)) {
            var default_attributes_and_events = this.getDefaultAttributesAndEventsForTags();

            for (var key in default_attributes_and_events) {
                var defaults = default_attributes_and_events[key];
                if (typeof defaults == 'object') {
                    var h = WYMeditor.Helper;
                    if ((defaults['except'] && h.contains(defaults['except'], tag)) || (defaults['only'] && !h.contains(defaults['only'], tag))) {
                        continue;
                    }

                    var tag_defaults = defaults['attributes'] ? defaults['attributes'] : defaults['events'];
                    for (var k in tag_defaults) {
                        default_attributes.push(typeof tag_defaults[k] != 'string' ? k : tag_defaults[k]);
                    }
                }
            }
        }
        return default_attributes;
    },
    doesAttributeNeedsValidation: function(tag, attribute)
    {
        return this._tags[tag] && ((this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute]) || (this._tags[tag]['required'] &&
                WYMeditor.Helper.contains(this._tags[tag]['required'], attribute)));
    },
    validateAttribute: function(tag, attribute, value)
    {
        if (this._tags[tag] &&
                (this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute] && value.length > 0 && !value.match(this._tags[tag]['attributes'][attribute])) || // invalid format
                (this._tags[tag] && this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute) && value.length == 0) // required attribute
                ) {
            return false;
        }
        return typeof this._tags[tag] != 'undefined';
    },
    getPossibleTagAttributes: function(tag)
    {
        if (!this._possible_tag_attributes) {
            this._possible_tag_attributes = {};
        }
        if (!this._possible_tag_attributes[tag]) {
            this._possible_tag_attributes[tag] = this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag));
        }
        return this._possible_tag_attributes[tag];
    }
};


/**
 *    Compounded regular expression. Any of
 *    the contained patterns could match and
 *    when one does, it's label is returned.
 *
 *    Constructor. Starts with no patterns.
 *    @param boolean case    True for case sensitive, false
 *                            for insensitive.
 *    @access public
 *    @author Marcus Baker (http://lastcraft.com)
 *    @author Bermi Ferrer (http://bermi.org)
 */
WYMeditor.ParallelRegex = function(case_sensitive)
{
    this._case = case_sensitive;
    this._patterns = [];
    this._labels = [];
    this._regex = null;
    return this;
};


/**
 *    Adds a pattern with an optional label.
 *    @param string pattern      Perl style regex, but ( and )
 *                                lose the usual meaning.
 *    @param string label        Label of regex to be returned
 *                                on a match.
 *    @access public
 */
WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label)
{
    label = label || true;
    var count = this._patterns.length;
    this._patterns[count] = pattern;
    this._labels[count] = label;
    this._regex = null;
};

/**
 *    Attempts to match all patterns at once against
 *    a string.
 *    @param string subject      String to match against.
 *
 *    @return boolean             True on success.
 *    @return string match         First matched portion of
 *                                subject.
 *    @access public
 */
WYMeditor.ParallelRegex.prototype.match = function(subject)
{
    if (this._patterns.length == 0) {
        return [false, ''];
    }
    var matches = subject.match(this._getCompoundedRegex());

    if (!matches) {
        return [false, ''];
    }
    var match = matches[0];
    for (var i = 1; i < matches.length; i++) {
        if (matches[i]) {
            return [this._labels[i - 1], match];
        }
    }
    return [true, matches[0]];
};

/**
 *    Compounds the patterns into a single
 *    regular expression separated with the
 *    "or" operator. Caches the regex.
 *    Will automatically escape (, ) and / tokens.
 *    @param array patterns    List of patterns in order.
 *    @access private
 */
WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function()
{
    if (this._regex == null) {
        for (var i = 0, count = this._patterns.length; i < count; i++) {
            this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g, '\\$1')) + ')';
        }
        this._regex = new RegExp(this._patterns.join("|"), this._getPerlMatchingFlags());
    }
    return this._regex;
};

/**
 * Escape lookahead/lookbehind blocks
 */
WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex)
{
    return regex.
            replace(/\(\?(i|m|s|x|U)\)/, '~~~~~~Tk1\$1~~~~~~').
            replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~').
            replace(/\(\?\=(.*)\)/, '~~~~~~Tk3\$1~~~~~~').
            replace(/\(\?\!(.*)\)/, '~~~~~~Tk4\$1~~~~~~').
            replace(/\(\?\<\=(.*)\)/, '~~~~~~Tk5\$1~~~~~~').
            replace(/\(\?\<\!(.*)\)/, '~~~~~~Tk6\$1~~~~~~').
            replace(/\(\?\:(.*)\)/, '~~~~~~Tk7\$1~~~~~~');
};

/**
 * Unscape lookahead/lookbehind blocks
 */
WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex)
{
    return regex.
            replace(/~~~~~~Tk1(.{1})~~~~~~/, "(?\$1)").
            replace(/~~~~~~Tk2(.{2})~~~~~~/, "(?\$1)").
            replace(/~~~~~~Tk3(.*)~~~~~~/, "(?=\$1)").
            replace(/~~~~~~Tk4(.*)~~~~~~/, "(?!\$1)").
            replace(/~~~~~~Tk5(.*)~~~~~~/, "(?<=\$1)").
            replace(/~~~~~~Tk6(.*)~~~~~~/, "(?<!\$1)").
            replace(/~~~~~~Tk7(.*)~~~~~~/, "(?:\$1)");
};


/**
 *    Accessor for perl regex mode flags to use.
 *    @return string       Perl regex flags.
 *    @access private
 */
WYMeditor.ParallelRegex.prototype._getPerlMatchingFlags = function()
{
    return (this._case ? "m" : "mi");
};



/**
 *    States for a stack machine.
 *
 *    Constructor. Starts in named state.
 *    @param string start        Starting state name.
 *    @access public
 *    @author Marcus Baker (http://lastcraft.com)
 *    @author Bermi Ferrer (http://bermi.org)
 */
WYMeditor.StateStack = function(start)
{
    this._stack = [start];
    return this;
};

/**
 *    Accessor for current state.
 *    @return string       State.
 *    @access public
 */
WYMeditor.StateStack.prototype.getCurrent = function()
{
    return this._stack[this._stack.length - 1];
};

/**
 *    Adds a state to the stack and sets it
 *    to be the current state.
 *    @param string state        New state.
 *    @access public
 */
WYMeditor.StateStack.prototype.enter = function(state)
{
    this._stack.push(state);
};

/**
 *    Leaves the current state and reverts
 *    to the previous one.
 *    @return boolean    False if we drop off
 *                       the bottom of the list.
 *    @access public
 */
WYMeditor.StateStack.prototype.leave = function()
{
    if (this._stack.length == 1) {
        return false;
    }
    this._stack.pop();
    return true;
};


// GLOBALS
WYMeditor.LEXER_ENTER = 1;
WYMeditor.LEXER_MATCHED = 2;
WYMeditor.LEXER_UNMATCHED = 3;
WYMeditor.LEXER_EXIT = 4;
WYMeditor.LEXER_SPECIAL = 5;


/**
 *    Accepts text and breaks it into tokens.
 *    Some optimisation to make the sure the
 *    content is only scanned by the PHP regex
 *    parser once. Lexer modes must not start
 *    with leading underscores.
 *
 *    Sets up the lexer in case insensitive matching
 *    by default.
 *    @param Parser parser  Handling strategy by reference.
 *    @param string start            Starting handler.
 *    @param boolean case            True for case sensitive.
 *    @access public
 *    @author Marcus Baker (http://lastcraft.com)
 *    @author Bermi Ferrer (http://bermi.org)
 */
WYMeditor.Lexer = function(parser, start, case_sensitive)
{
    start = start || 'accept';
    this._case = case_sensitive || false;
    this._regexes = {};
    this._parser = parser;
    this._mode = new WYMeditor.StateStack(start);
    this._mode_handlers = {};
    this._mode_handlers[start] = start;
    return this;
};

/**
 *    Adds a token search pattern for a particular
 *    parsing mode. The pattern does not change the
 *    current mode.
 *    @param string pattern      Perl style regex, but ( and )
 *                                lose the usual meaning.
 *    @param string mode         Should only apply this
 *                                pattern when dealing with
 *                                this type of input.
 *    @access public
 */
WYMeditor.Lexer.prototype.addPattern = function(pattern, mode)
{
    var mode = mode || "accept";
    if (typeof this._regexes[mode] == 'undefined') {
        this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
    }
    this._regexes[mode].addPattern(pattern);
    if (typeof this._mode_handlers[mode] == 'undefined') {
        this._mode_handlers[mode] = mode;
    }
};

/**
 *    Adds a pattern that will enter a new parsing
 *    mode. Useful for entering parenthesis, strings,
 *    tags, etc.
 *    @param string pattern      Perl style regex, but ( and )
 *                                lose the usual meaning.
 *    @param string mode         Should only apply this
 *                                pattern when dealing with
 *                                this type of input.
 *    @param string new_mode     Change parsing to this new
 *                                nested mode.
 *    @access public
 */
WYMeditor.Lexer.prototype.addEntryPattern = function(pattern, mode, new_mode)
{
    if (typeof this._regexes[mode] == 'undefined') {
        this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
    }
    this._regexes[mode].addPattern(pattern, new_mode);
    if (typeof this._mode_handlers[new_mode] == 'undefined') {
        this._mode_handlers[new_mode] = new_mode;
    }
};

/**
 *    Adds a pattern that will exit the current mode
 *    and re-enter the previous one.
 *    @param string pattern      Perl style regex, but ( and )
 *                                lose the usual meaning.
 *    @param string mode         Mode to leave.
 *    @access public
 */
WYMeditor.Lexer.prototype.addExitPattern = function(pattern, mode)
{
    if (typeof this._regexes[mode] == 'undefined') {
        this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
    }
    this._regexes[mode].addPattern(pattern, "__exit");
    if (typeof this._mode_handlers[mode] == 'undefined') {
        this._mode_handlers[mode] = mode;
    }
};

/**
 *    Adds a pattern that has a special mode. Acts as an entry
 *    and exit pattern in one go, effectively calling a special
 *    parser handler for this token only.
 *    @param string pattern      Perl style regex, but ( and )
 *                                lose the usual meaning.
 *    @param string mode         Should only apply this
 *                                pattern when dealing with
 *                                this type of input.
 *    @param string special      Use this mode for this one token.
 *    @access public
 */
WYMeditor.Lexer.prototype.addSpecialPattern = function(pattern, mode, special)
{
    if (typeof this._regexes[mode] == 'undefined') {
        this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
    }
    this._regexes[mode].addPattern(pattern, '_' + special);
    if (typeof this._mode_handlers[special] == 'undefined') {
        this._mode_handlers[special] = special;
    }
};

/**
 *    Adds a mapping from a mode to another handler.
 *    @param string mode        Mode to be remapped.
 *    @param string handler     New target handler.
 *    @access public
 */
WYMeditor.Lexer.prototype.mapHandler = function(mode, handler)
{
    this._mode_handlers[mode] = handler;
};

/**
 *    Splits the page text into tokens. Will fail
 *    if the handlers report an error or if no
 *    content is consumed. If successful then each
 *    unparsed and parsed token invokes a call to the
 *    held listener.
 *    @param string raw        Raw HTML text.
 *    @return boolean           True on success, else false.
 *    @access public
 */
WYMeditor.Lexer.prototype.parse = function(raw)
{
    if (typeof this._parser == 'undefined') {
        return false;
    }

    var length = raw.length;
    var parsed;
    while (typeof (parsed = this._reduce(raw)) == 'object') {
        var raw = parsed[0];
        var unmatched = parsed[1];
        var matched = parsed[2];
        var mode = parsed[3];

        if (!this._dispatchTokens(unmatched, matched, mode)) {
            return false;
        }

        if (raw == '') {
            return true;
        }
        if (raw.length == length) {
            return false;
        }
        length = raw.length;
    }
    if (!parsed) {
        return false;
    }

    return this._invokeParser(raw, WYMeditor.LEXER_UNMATCHED);
};

/**
 *    Sends the matched token and any leading unmatched
 *    text to the parser changing the lexer to a new
 *    mode if one is listed.
 *    @param string unmatched    Unmatched leading portion.
 *    @param string matched      Actual token match.
 *    @param string mode         Mode after match. A boolean
 *                                false mode causes no change.
 *    @return boolean             False if there was any error
 *                                from the parser.
 *    @access private
 */
WYMeditor.Lexer.prototype._dispatchTokens = function(unmatched, matched, mode)
{
    mode = mode || false;

    if (!this._invokeParser(unmatched, WYMeditor.LEXER_UNMATCHED)) {
        return false;
    }

    if (typeof mode == 'boolean') {
        return this._invokeParser(matched, WYMeditor.LEXER_MATCHED);
    }
    if (this._isModeEnd(mode)) {
        if (!this._invokeParser(matched, WYMeditor.LEXER_EXIT)) {
            return false;
        }
        return this._mode.leave();
    }
    if (this._isSpecialMode(mode)) {
        this._mode.enter(this._decodeSpecial(mode));
        if (!this._invokeParser(matched, WYMeditor.LEXER_SPECIAL)) {
            return false;
        }
        return this._mode.leave();
    }
    this._mode.enter(mode);

    return this._invokeParser(matched, WYMeditor.LEXER_ENTER);
};

/**
 *    Tests to see if the new mode is actually to leave
 *    the current mode and pop an item from the matching
 *    mode stack.
 *    @param string mode    Mode to test.
 *    @return boolean        True if this is the exit mode.
 *    @access private
 */
WYMeditor.Lexer.prototype._isModeEnd = function(mode)
{
    return (mode === "__exit");
};

/**
 *    Test to see if the mode is one where this mode
 *    is entered for this token only and automatically
 *    leaves immediately afterwoods.
 *    @param string mode    Mode to test.
 *    @return boolean        True if this is the exit mode.
 *    @access private
 */
WYMeditor.Lexer.prototype._isSpecialMode = function(mode)
{
    return (mode.substring(0, 1) == "_");
};

/**
 *    Strips the magic underscore marking single token
 *    modes.
 *    @param string mode    Mode to decode.
 *    @return string         Underlying mode name.
 *    @access private
 */
WYMeditor.Lexer.prototype._decodeSpecial = function(mode)
{
    return mode.substring(1);
};

/**
 *    Calls the parser method named after the current
 *    mode. Empty content will be ignored. The lexer
 *    has a parser handler for each mode in the lexer.
 *    @param string content        Text parsed.
 *    @param boolean is_match      Token is recognised rather
 *                                  than unparsed data.
 *    @access private
 */
WYMeditor.Lexer.prototype._invokeParser = function(content, is_match)
{

    if (content === '') {
        return true;
    }
    var current = this._mode.getCurrent();
    var handler = this._mode_handlers[current];
    var result;
    eval('result = this._parser.' + handler + '(content, is_match);');
    return result;
};

/**
 *    Tries to match a chunk of text and if successful
 *    removes the recognised chunk and any leading
 *    unparsed data. Empty strings will not be matched.
 *    @param string raw         The subject to parse. This is the
 *                               content that will be eaten.
 *    @return array/boolean      Three item list of unparsed
 *                               content followed by the
 *                               recognised token and finally the
 *                               action the parser is to take.
 *                               True if no match, false if there
 *                               is a parsing error.
 *    @access private
 */
WYMeditor.Lexer.prototype._reduce = function(raw)
{
    var matched = this._regexes[this._mode.getCurrent()].match(raw);
    var match = matched[1];
    var action = matched[0];
    if (action) {
        var unparsed_character_count = raw.indexOf(match);
        var unparsed = raw.substr(0, unparsed_character_count);
        raw = raw.substring(unparsed_character_count + match.length);
        return [raw, unparsed, match, action];
    }
    return true;
};



/**
 * This are the rules for breaking the XHTML code into events
 * handled by the provided parser.
 *
 *    @author Marcus Baker (http://lastcraft.com)
 *    @author Bermi Ferrer (http://bermi.org)
 */
WYMeditor.XhtmlLexer = function(parser)
{
    jQuery.extend(this, new WYMeditor.Lexer(parser, 'Text'));

    this.mapHandler('Text', 'Text');

    this.addTokens();

    this.init();

    return this;
};


WYMeditor.XhtmlLexer.prototype.init = function()
{
};

WYMeditor.XhtmlLexer.prototype.addTokens = function()
{
    this.addCommentTokens('Text');
    this.addScriptTokens('Text');
    this.addCssTokens('Text');
    this.addTagTokens('Text');
};

WYMeditor.XhtmlLexer.prototype.addCommentTokens = function(scope)
{
    this.addEntryPattern("<!--", scope, 'Comment');
    this.addExitPattern("-->", 'Comment');
};

WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope)
{
    this.addEntryPattern("<script", scope, 'Script');
    this.addExitPattern("</script>", 'Script');
};

WYMeditor.XhtmlLexer.prototype.addCssTokens = function(scope)
{
    this.addEntryPattern("<style", scope, 'Css');
    this.addExitPattern("</style>", 'Css');
};

WYMeditor.XhtmlLexer.prototype.addTagTokens = function(scope)
{
    this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*>", scope, 'OpeningTag');
    this.addEntryPattern("<[a-z0-9:\-]+" + '[\\\/ \\\>]+', scope, 'OpeningTag');
    this.addInTagDeclarationTokens('OpeningTag');

    this.addSpecialPattern("</\\s*[a-z0-9:\-]+\\s*>", scope, 'ClosingTag');

};

WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens = function(scope)
{
    this.addSpecialPattern('\\s+', scope, 'Ignore');

    this.addAttributeTokens(scope);

    this.addExitPattern('/>', scope);
    this.addExitPattern('>', scope);

};

WYMeditor.XhtmlLexer.prototype.addAttributeTokens = function(scope)
{
    this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?=\=)\\s*", scope, 'TagAttributes');

    this.addEntryPattern('=\\s*"', scope, 'DoubleQuotedAttribute');
    this.addPattern("\\\\\"", 'DoubleQuotedAttribute');
    this.addExitPattern('"', 'DoubleQuotedAttribute');

    this.addEntryPattern("=\\s*'", scope, 'SingleQuotedAttribute');
    this.addPattern("\\\\'", 'SingleQuotedAttribute');
    this.addExitPattern("'", 'SingleQuotedAttribute');

    this.addSpecialPattern('=\\s*[^>\\s]*', scope, 'UnquotedAttribute');
};



/**
 * XHTML Parser.
 *
 * This XHTML parser will trigger the events available on on
 * current SaxListener
 *
 *    @author Bermi Ferrer (http://bermi.org)
 */
WYMeditor.XhtmlParser = function(Listener, mode)
{
    var mode = mode || 'Text';
    this._Lexer = new WYMeditor.XhtmlLexer(this);
    this._Listener = Listener;
    this._mode = mode;
    this._matches = [];
    this._last_match = '';
    this._current_match = '';

    return this;
};

WYMeditor.XhtmlParser.prototype.parse = function(raw)
{
    this._Lexer.parse(this.beforeParsing(raw));
    return this.afterParsing(this._Listener.getResult());
};

WYMeditor.XhtmlParser.prototype.beforeParsing = function(raw)
{
    if (raw.match(/class="MsoNormal"/) || raw.match(/ns = "urn:schemas-microsoft-com/)) {
        // Usefull for cleaning up content pasted from other sources (MSWord)
        this._Listener.avoidStylingTagsAndAttributes();
    }
    return this._Listener.beforeParsing(raw);
};

WYMeditor.XhtmlParser.prototype.afterParsing = function(parsed)
{
    if (this._Listener._avoiding_tags_implicitly) {
        this._Listener.allowStylingTagsAndAttributes();
    }
    return this._Listener.afterParsing(parsed);
};


WYMeditor.XhtmlParser.prototype.Ignore = function(match, state)
{
    return true;
};

WYMeditor.XhtmlParser.prototype.Text = function(text)
{
    this._Listener.addContent(text);
    return true;
};

WYMeditor.XhtmlParser.prototype.Comment = function(match, status)
{
    return this._addNonTagBlock(match, status, 'addComment');
};

WYMeditor.XhtmlParser.prototype.Script = function(match, status)
{
    return this._addNonTagBlock(match, status, 'addScript');
};

WYMeditor.XhtmlParser.prototype.Css = function(match, status)
{
    return this._addNonTagBlock(match, status, 'addCss');
};

WYMeditor.XhtmlParser.prototype._addNonTagBlock = function(match, state, type)
{
    switch (state) {
        case WYMeditor.LEXER_ENTER:
            this._non_tag = match;
            break;
        case WYMeditor.LEXER_UNMATCHED:
            this._non_tag += match;
            break;
        case WYMeditor.LEXER_EXIT:
            switch (type) {
                case 'addComment':
                    this._Listener.addComment(this._non_tag + match);
                    break;
                case 'addScript':
                    this._Listener.addScript(this._non_tag + match);
                    break;
                case 'addCss':
                    this._Listener.addCss(this._non_tag + match);
                    break;
            }
    }
    return true;
};

WYMeditor.XhtmlParser.prototype.OpeningTag = function(match, state)
{
    switch (state) {
        case WYMeditor.LEXER_ENTER:
            this._tag = this.normalizeTag(match);
            this._tag_attributes = {};
            break;
        case WYMeditor.LEXER_SPECIAL:
            this._callOpenTagListener(this.normalizeTag(match));
            break;
        case WYMeditor.LEXER_EXIT:
            this._callOpenTagListener(this._tag, this._tag_attributes);
    }
    return true;
};

WYMeditor.XhtmlParser.prototype.ClosingTag = function(match, state)
{
    this._callCloseTagListener(this.normalizeTag(match));
    return true;
};

WYMeditor.XhtmlParser.prototype._callOpenTagListener = function(tag, attributes)
{
    var attributes = attributes || {};
    this.autoCloseUnclosedBeforeNewOpening(tag);

    if (this._Listener.isBlockTag(tag)) {
        this._Listener._tag_stack.push(tag);
        this._Listener.fixNestingBeforeOpeningBlockTag(tag, attributes);
        this._Listener.openBlockTag(tag, attributes);
        this._increaseOpenTagCounter(tag);
    } else if (this._Listener.isInlineTag(tag)) {
        this._Listener.inlineTag(tag, attributes);
    } else {
        this._Listener.openUnknownTag(tag, attributes);
        this._increaseOpenTagCounter(tag);
    }
    this._Listener.last_tag = tag;
    this._Listener.last_tag_opened = true;
    this._Listener.last_tag_attributes = attributes;
};

WYMeditor.XhtmlParser.prototype._callCloseTagListener = function(tag)
{
    if (this._decreaseOpenTagCounter(tag)) {
        this.autoCloseUnclosedBeforeTagClosing(tag);

        if (this._Listener.isBlockTag(tag)) {
            var expected_tag = this._Listener._tag_stack.pop();
            if (expected_tag == false) {
                return;
            } else if (expected_tag != tag) {
                tag = expected_tag;
            }
            this._Listener.closeBlockTag(tag);
        } else {
            this._Listener.closeUnknownTag(tag);
        }
    } else {
        this._Listener.closeUnopenedTag(tag);
    }
    this._Listener.last_tag = tag;
    this._Listener.last_tag_opened = false;
};

WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter = function(tag)
{
    this._Listener._open_tags[tag] = this._Listener._open_tags[tag] || 0;
    this._Listener._open_tags[tag]++;
};

WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter = function(tag)
{
    if (this._Listener._open_tags[tag]) {
        this._Listener._open_tags[tag]--;
        if (this._Listener._open_tags[tag] == 0) {
            this._Listener._open_tags[tag] = undefined;
        }
        return true;
    }
    return false;
};

WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening = function(new_tag)
{
    this._autoCloseUnclosed(new_tag, false);
};

WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing = function(tag)
{
    this._autoCloseUnclosed(tag, true);
};

WYMeditor.XhtmlParser.prototype._autoCloseUnclosed = function(new_tag, closing)
{
    var closing = closing || false;
    if (this._Listener._open_tags) {
        for (var tag in this._Listener._open_tags) {
            var counter = this._Listener._open_tags[tag];
            if (counter > 0 && this._Listener.shouldCloseTagAutomatically(tag, new_tag, closing)) {
                this._callCloseTagListener(tag, true);
            }
        }
    }
};

WYMeditor.XhtmlParser.prototype.getTagReplacements = function()
{
    return this._Listener.getTagReplacements();
};

WYMeditor.XhtmlParser.prototype.normalizeTag = function(tag)
{
    tag = tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm, '').toLowerCase();
    var tags = this._Listener.getTagReplacements();
    if (tags[tag]) {
        return tags[tag];
    }
    return tag;
};

WYMeditor.XhtmlParser.prototype.TagAttributes = function(match, state)
{
    if (WYMeditor.LEXER_SPECIAL == state) {
        this._current_attribute = match;
    }
    return true;
};

WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute = function(match, state)
{
    if (WYMeditor.LEXER_UNMATCHED == state) {
        this._tag_attributes[this._current_attribute] = match;
    }
    return true;
};

WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute = function(match, state)
{
    if (WYMeditor.LEXER_UNMATCHED == state) {
        this._tag_attributes[this._current_attribute] = match;
    }
    return true;
};

WYMeditor.XhtmlParser.prototype.UnquotedAttribute = function(match, state)
{
    this._tag_attributes[this._current_attribute] = match.replace(/^=/, '');
    return true;
};



/**
 * XHTML Sax parser.
 *
 *    @author Bermi Ferrer (http://bermi.org)
 */
WYMeditor.XhtmlSaxListener = function()
{
    this.output = '';
    this.helper = new WYMeditor.XmlHelper();
    this._open_tags = {};
    this.validator = WYMeditor.XhtmlValidator;
    this._tag_stack = [];
    this.avoided_tags = [];

    this.entities = {
        '&nbsp;': '&#160;', '&iexcl;': '&#161;', '&cent;': '&#162;',
        '&pound;': '&#163;', '&curren;': '&#164;', '&yen;': '&#165;',
        '&brvbar;': '&#166;', '&sect;': '&#167;', '&uml;': '&#168;',
        '&copy;': '&#169;', '&ordf;': '&#170;', '&laquo;': '&#171;',
        '&not;': '&#172;', '&shy;': '&#173;', '&reg;': '&#174;',
        '&macr;': '&#175;', '&deg;': '&#176;', '&plusmn;': '&#177;',
        '&sup2;': '&#178;', '&sup3;': '&#179;', '&acute;': '&#180;',
        '&micro;': '&#181;', '&para;': '&#182;', '&middot;': '&#183;',
        '&cedil;': '&#184;', '&sup1;': '&#185;', '&ordm;': '&#186;',
        '&raquo;': '&#187;', '&frac14;': '&#188;', '&frac12;': '&#189;',
        '&frac34;': '&#190;', '&iquest;': '&#191;', '&Agrave;': '&#192;',
        '&Aacute;': '&#193;', '&Acirc;': '&#194;', '&Atilde;': '&#195;',
        '&Auml;': '&#196;', '&Aring;': '&#197;', '&AElig;': '&#198;',
        '&Ccedil;': '&#199;', '&Egrave;': '&#200;', '&Eacute;': '&#201;',
        '&Ecirc;': '&#202;', '&Euml;': '&#203;', '&Igrave;': '&#204;',
        '&Iacute;': '&#205;', '&Icirc;': '&#206;', '&Iuml;': '&#207;',
        '&ETH;': '&#208;', '&Ntilde;': '&#209;', '&Ograve;': '&#210;',
        '&Oacute;': '&#211;', '&Ocirc;': '&#212;', '&Otilde;': '&#213;',
        '&Ouml;': '&#214;', '&times;': '&#215;', '&Oslash;': '&#216;',
        '&Ugrave;': '&#217;', '&Uacute;': '&#218;', '&Ucirc;': '&#219;',
        '&Uuml;': '&#220;', '&Yacute;': '&#221;', '&THORN;': '&#222;',
        '&szlig;': '&#223;', '&agrave;': '&#224;', '&aacute;': '&#225;',
        '&acirc;': '&#226;', '&atilde;': '&#227;', '&auml;': '&#228;',
        '&aring;': '&#229;', '&aelig;': '&#230;', '&ccedil;': '&#231;',
        '&egrave;': '&#232;', '&eacute;': '&#233;', '&ecirc;': '&#234;',
        '&euml;': '&#235;', '&igrave;': '&#236;', '&iacute;': '&#237;',
        '&icirc;': '&#238;', '&iuml;': '&#239;', '&eth;': '&#240;',
        '&ntilde;': '&#241;', '&ograve;': '&#242;', '&oacute;': '&#243;',
        '&ocirc;': '&#244;', '&otilde;': '&#245;', '&ouml;': '&#246;',
        '&divide;': '&#247;', '&oslash;': '&#248;', '&ugrave;': '&#249;',
        '&uacute;': '&#250;', '&ucirc;': '&#251;', '&uuml;': '&#252;',
        '&yacute;': '&#253;', '&thorn;': '&#254;', '&yuml;': '&#255;',
        '&OElig;': '&#338;', '&oelig;': '&#339;', '&Scaron;': '&#352;',
        '&scaron;': '&#353;', '&Yuml;': '&#376;', '&fnof;': '&#402;',
        '&circ;': '&#710;', '&tilde;': '&#732;', '&Alpha;': '&#913;',
        '&Beta;': '&#914;', '&Gamma;': '&#915;', '&Delta;': '&#916;',
        '&Epsilon;': '&#917;', '&Zeta;': '&#918;', '&Eta;': '&#919;',
        '&Theta;': '&#920;', '&Iota;': '&#921;', '&Kappa;': '&#922;',
        '&Lambda;': '&#923;', '&Mu;': '&#924;', '&Nu;': '&#925;',
        '&Xi;': '&#926;', '&Omicron;': '&#927;', '&Pi;': '&#928;',
        '&Rho;': '&#929;', '&Sigma;': '&#931;', '&Tau;': '&#932;',
        '&Upsilon;': '&#933;', '&Phi;': '&#934;', '&Chi;': '&#935;',
        '&Psi;': '&#936;', '&Omega;': '&#937;', '&alpha;': '&#945;',
        '&beta;': '&#946;', '&gamma;': '&#947;', '&delta;': '&#948;',
        '&epsilon;': '&#949;', '&zeta;': '&#950;', '&eta;': '&#951;',
        '&theta;': '&#952;', '&iota;': '&#953;', '&kappa;': '&#954;',
        '&lambda;': '&#955;', '&mu;': '&#956;', '&nu;': '&#957;',
        '&xi;': '&#958;', '&omicron;': '&#959;', '&pi;': '&#960;',
        '&rho;': '&#961;', '&sigmaf;': '&#962;', '&sigma;': '&#963;',
        '&tau;': '&#964;', '&upsilon;': '&#965;', '&phi;': '&#966;',
        '&chi;': '&#967;', '&psi;': '&#968;', '&omega;': '&#969;',
        '&thetasym;': '&#977;', '&upsih;': '&#978;', '&piv;': '&#982;',
        '&ensp;': '&#8194;', '&emsp;': '&#8195;', '&thinsp;': '&#8201;',
        '&zwnj;': '&#8204;', '&zwj;': '&#8205;', '&lrm;': '&#8206;',
        '&rlm;': '&#8207;', '&ndash;': '&#8211;', '&mdash;': '&#8212;',
        '&lsquo;': '&#8216;', '&rsquo;': '&#8217;', '&sbquo;': '&#8218;',
        '&ldquo;': '&#8220;', '&rdquo;': '&#8221;', '&bdquo;': '&#8222;',
        '&dagger;': '&#8224;', '&Dagger;': '&#8225;', '&bull;': '&#8226;',
        '&hellip;': '&#8230;', '&permil;': '&#8240;', '&prime;': '&#8242;',
        '&Prime;': '&#8243;', '&lsaquo;': '&#8249;', '&rsaquo;': '&#8250;',
        '&oline;': '&#8254;', '&frasl;': '&#8260;', '&euro;': '&#8364;',
        '&image;': '&#8465;', '&weierp;': '&#8472;', '&real;': '&#8476;',
        '&trade;': '&#8482;', '&alefsym;': '&#8501;', '&larr;': '&#8592;',
        '&uarr;': '&#8593;', '&rarr;': '&#8594;', '&darr;': '&#8595;',
        '&harr;': '&#8596;', '&crarr;': '&#8629;', '&lArr;': '&#8656;',
        '&uArr;': '&#8657;', '&rArr;': '&#8658;', '&dArr;': '&#8659;',
        '&hArr;': '&#8660;', '&forall;': '&#8704;', '&part;': '&#8706;',
        '&exist;': '&#8707;', '&empty;': '&#8709;', '&nabla;': '&#8711;',
        '&isin;': '&#8712;', '&notin;': '&#8713;', '&ni;': '&#8715;',
        '&prod;': '&#8719;', '&sum;': '&#8721;', '&minus;': '&#8722;',
        '&lowast;': '&#8727;', '&radic;': '&#8730;', '&prop;': '&#8733;',
        '&infin;': '&#8734;', '&ang;': '&#8736;', '&and;': '&#8743;',
        '&or;': '&#8744;', '&cap;': '&#8745;', '&cup;': '&#8746;',
        '&int;': '&#8747;', '&there4;': '&#8756;', '&sim;': '&#8764;',
        '&cong;': '&#8773;', '&asymp;': '&#8776;', '&ne;': '&#8800;',
        '&equiv;': '&#8801;', '&le;': '&#8804;', '&ge;': '&#8805;',
        '&sub;': '&#8834;', '&sup;': '&#8835;', '&nsub;': '&#8836;',
        '&sube;': '&#8838;', '&supe;': '&#8839;', '&oplus;': '&#8853;',
        '&otimes;': '&#8855;', '&perp;': '&#8869;', '&sdot;': '&#8901;',
        '&lceil;': '&#8968;', '&rceil;': '&#8969;', '&lfloor;': '&#8970;',
        '&rfloor;': '&#8971;', '&lang;': '&#9001;', '&rang;': '&#9002;',
        '&loz;': '&#9674;', '&spades;': '&#9824;', '&clubs;': '&#9827;',
        '&hearts;': '&#9829;', '&diams;': '&#9830;'};

    this.block_tags = ["a", "abbr", "acronym", "address", "area", "b",
        "base", "bdo", "big", "blockquote", "body", "button",
        "caption", "cite", "code", "col", "colgroup", "dd", "del", "div",
        "dfn", "dl", "dt", "em", "fieldset", "form", "head", "h1", "h2",
        "h3", "h4", "h5", "h6", "html", "i", "ins",
        "kbd", "label", "legend", "li", "map", "noscript",
        "object", "ol", "optgroup", "option", "p", "param", "pre", "q",
        "samp", "script", "select", "small", "span", "strong", "style",
        "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th",
        "thead", "title", "tr", "tt", "ul", "var", "extends"];


    this.inline_tags = ["br", "hr", "img", "input"];

    return this;
};

WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically = function(tag, now_on_tag, closing)
{
    var closing = closing || false;
    if (tag == 'td') {
        if ((closing && now_on_tag == 'tr') || (!closing && now_on_tag == 'td')) {
            return true;
        }
    }
    if (tag == 'option') {
        if ((closing && now_on_tag == 'select') || (!closing && now_on_tag == 'option')) {
            return true;
        }
    }
    return false;
};

WYMeditor.XhtmlSaxListener.prototype.beforeParsing = function(raw)
{
    this.output = '';
    return raw;
};

WYMeditor.XhtmlSaxListener.prototype.afterParsing = function(xhtml)
{
    xhtml = this.replaceNamedEntities(xhtml);
    xhtml = this.joinRepeatedEntities(xhtml);
    xhtml = this.removeEmptyTags(xhtml);
    xhtml = this.removeBrInPre(xhtml);
    return xhtml;
};

WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities = function(xhtml)
{
    for (var entity in this.entities) {
        xhtml = xhtml.replace(new RegExp(entity, 'g'), this.entities[entity]);
    }
    return xhtml;
};

WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities = function(xhtml)
{
    var tags = 'em|strong|sub|sup|acronym|pre|del|address';
    return xhtml.replace(new RegExp('<\/(' + tags + ')><\\1>', ''), '').
            replace(new RegExp('(\s*<(' + tags + ')>\s*){2}(.*)(\s*<\/\\2>\s*){2}', ''), '<\$2>\$3<\$2>');
};

WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags = function(xhtml)
{
    return xhtml.replace(new RegExp('<(' + this.block_tags.join("|").replace(/\|td/, '').replace(/\|th/, '') + ')>(<br \/>|&#160;|&nbsp;|\\s)*<\/\\1>', 'g'), '');
};

WYMeditor.XhtmlSaxListener.prototype.removeBrInPre = function(xhtml)
{
    var matches = xhtml.match(new RegExp('<pre[^>]*>(.*?)<\/pre>', 'gmi'));
    if (matches) {
        for (var i = 0; i < matches.length; i++) {
            xhtml = xhtml.replace(matches[i], matches[i].replace(new RegExp('<br \/>', 'g'), String.fromCharCode(13, 10)));
        }
    }
    return xhtml;
};

WYMeditor.XhtmlSaxListener.prototype.getResult = function()
{
    return this.output;
};

WYMeditor.XhtmlSaxListener.prototype.getTagReplacements = function()
{
    return {'b': 'strong', 'i': 'em'};
};

WYMeditor.XhtmlSaxListener.prototype.addContent = function(text)
{
    this.output += text;
};

WYMeditor.XhtmlSaxListener.prototype.addComment = function(text)
{
    if (this.remove_comments) {
        this.output += text;
    }
};

WYMeditor.XhtmlSaxListener.prototype.addScript = function(text)
{
    if (!this.remove_scripts) {
        this.output += text;
    }
};

WYMeditor.XhtmlSaxListener.prototype.addCss = function(text)
{
    if (!this.remove_embeded_styles) {
        this.output += text;
    }
};

WYMeditor.XhtmlSaxListener.prototype.openBlockTag = function(tag, attributes)
{
    this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes), true);
};

WYMeditor.XhtmlSaxListener.prototype.inlineTag = function(tag, attributes)
{
    this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes));
};

WYMeditor.XhtmlSaxListener.prototype.openUnknownTag = function(tag, attributes)
{
    //this.output += this.helper.tag(tag, attributes, true);
};

WYMeditor.XhtmlSaxListener.prototype.closeBlockTag = function(tag)
{
    this.output = this.output.replace(/<br \/>$/, '') + this._getClosingTagContent('before', tag) + "</" + tag + ">" + this._getClosingTagContent('after', tag);
};

WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag = function(tag)
{
    //this.output += "</"+tag+">";
};

WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag = function(tag)
{
    this.output += "</" + tag + ">";
};

WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes = function()
{
    this.avoided_tags = ['div', 'span'];
    this.validator.skiped_attributes = ['style'];
    this.validator.skiped_attribute_values = ['MsoNormal', 'main1']; // MS Word attributes for class
    this._avoiding_tags_implicitly = true;
};

WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes = function()
{
    this.avoided_tags = [];
    this.validator.skiped_attributes = [];
    this.validator.skiped_attribute_values = [];
    this._avoiding_tags_implicitly = false;
};

WYMeditor.XhtmlSaxListener.prototype.isBlockTag = function(tag)
{
    return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.block_tags, tag);
};

WYMeditor.XhtmlSaxListener.prototype.isInlineTag = function(tag)
{
    return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.inline_tags, tag);
};

WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag = function(tag, content)
{
    this._insertContentWhenClosingTag('after', tag, content);
};

WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag = function(tag, content)
{
    this._insertContentWhenClosingTag('before', tag, content);
};

WYMeditor.XhtmlSaxListener.prototype.fixNestingBeforeOpeningBlockTag = function(tag, attributes)
{
    if (tag != 'li' && (tag == 'ul' || tag == 'ol') && this.last_tag && !this.last_tag_opened && this.last_tag == 'li') {
        this.output = this.output.replace(/<\/li>$/, '');
        this.insertContentAfterClosingTag(tag, '</li>');
    }
};

WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag = function(position, tag, content)
{
    if (!this['_insert_' + position + '_closing']) {
        this['_insert_' + position + '_closing'] = [];
    }
    if (!this['_insert_' + position + '_closing'][tag]) {
        this['_insert_' + position + '_closing'][tag] = [];
    }
    this['_insert_' + position + '_closing'][tag].push(content);
};

WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent = function(position, tag)
{
    if (this['_insert_' + position + '_closing'] &&
            this['_insert_' + position + '_closing'][tag] &&
            this['_insert_' + position + '_closing'][tag].length > 0) {
        return this['_insert_' + position + '_closing'][tag].pop();
    }
    return '';
};


/********** CSS PARSER **********/


WYMeditor.WymCssLexer = function(parser, only_wym_blocks)
{
    var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? true : only_wym_blocks);

    jQuery.extend(this, new WYMeditor.Lexer(parser, (only_wym_blocks ? 'Ignore' : 'WymCss')));

    this.mapHandler('WymCss', 'Ignore');

    if (only_wym_blocks == true) {
        this.addEntryPattern("/\\\x2a[<\\s]*WYMeditor[>\\s]*\\\x2a/", 'Ignore', 'WymCss');
        this.addExitPattern("/\\\x2a[<\/\\s]*WYMeditor[>\\s]*\\\x2a/", 'WymCss');
    }

    this.addSpecialPattern("[\\sa-z1-6]*\\\x2e[a-z-_0-9]+", 'WymCss', 'WymCssStyleDeclaration');

    this.addEntryPattern("/\\\x2a", 'WymCss', 'WymCssComment');
    this.addExitPattern("\\\x2a/", 'WymCssComment');

    this.addEntryPattern("\x7b", 'WymCss', 'WymCssStyle');
    this.addExitPattern("\x7d", 'WymCssStyle');

    this.addEntryPattern("/\\\x2a", 'WymCssStyle', 'WymCssFeedbackStyle');
    this.addExitPattern("\\\x2a/", 'WymCssFeedbackStyle');

    return this;
};

WYMeditor.WymCssParser = function()
{
    this._in_style = false;
    this._has_title = false;
    this.only_wym_blocks = true;
    this.css_settings = {'classesItems': [], 'editorStyles': [], 'dialogStyles': []};
    return this;
};

WYMeditor.WymCssParser.prototype.parse = function(raw, only_wym_blocks)
{
    var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? this.only_wym_blocks : only_wym_blocks);
    this._Lexer = new WYMeditor.WymCssLexer(this, only_wym_blocks);
    this._Lexer.parse(raw);
};

WYMeditor.WymCssParser.prototype.Ignore = function(match, state)
{
    return true;
};

WYMeditor.WymCssParser.prototype.WymCssComment = function(text, status)
{
    if (text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/mi)) {
        return false;
    }
    if (status == WYMeditor.LEXER_UNMATCHED) {
        if (!this._in_style) {
            this._has_title = true;
            this._current_item = {'title': WYMeditor.Helper.trim(text)};
        } else {
            if (this._current_item[this._current_element]) {
                if (!this._current_item[this._current_element].expressions) {
                    this._current_item[this._current_element].expressions = [text];
                } else {
                    this._current_item[this._current_element].expressions.push(text);
                }
            }
        }
        this._in_style = true;
    }
    return true;
};

WYMeditor.WymCssParser.prototype.WymCssStyle = function(match, status)
{
    if (status == WYMeditor.LEXER_UNMATCHED) {
        match = WYMeditor.Helper.trim(match);
        if (match != '') {
            this._current_item[this._current_element].style = match;
        }
    } else if (status == WYMeditor.LEXER_EXIT) {
        this._in_style = false;
        this._has_title = false;
        this.addStyleSetting(this._current_item);
    }
    return true;
};

WYMeditor.WymCssParser.prototype.WymCssFeedbackStyle = function(match, status)
{
    if (status == WYMeditor.LEXER_UNMATCHED) {
        this._current_item[this._current_element].feedback_style = match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm, '');
    }
    return true;
};

WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration = function(match)
{
    match = match.replace(/^([\s\.]*)|([\s\.*]*)$/gm, '');

    var tag = '';
    if (match.indexOf('.') > 0) {
        var parts = match.split('.');
        this._current_element = parts[1];
        var tag = parts[0];
    } else {
        this._current_element = match;
    }

    if (!this._has_title) {
        this._current_item = {'title': (!tag ? '' : tag.toUpperCase() + ': ') + this._current_element};
        this._has_title = true;
    }

    if (!this._current_item[this._current_element]) {
        this._current_item[this._current_element] = {'name': this._current_element};
    }
    if (tag) {
        if (!this._current_item[this._current_element].tags) {
            this._current_item[this._current_element].tags = [tag];
        } else {
            this._current_item[this._current_element].tags.push(tag);
        }
    }
    return true;
};

WYMeditor.WymCssParser.prototype.addStyleSetting = function(style_details)
{
    for (var name in style_details) {
        var details = style_details[name];
        if (typeof details == 'object' && name != 'title') {

            this.css_settings.classesItems.push({
                'name': WYMeditor.Helper.trim(details.name),
                'title': style_details.title,
                'expr': WYMeditor.Helper.trim((details.expressions || details.tags).join(', '))
            });
            if (details.feedback_style) {
                this.css_settings.editorStyles.push({
                    'name': '.' + WYMeditor.Helper.trim(details.name),
                    'css': details.feedback_style
                });
            }
            if (details.style) {
                this.css_settings.dialogStyles.push({
                    'name': '.' + WYMeditor.Helper.trim(details.name),
                    'css': details.style
                });
            }
        }
    }
};

/********** HELPERS **********/

// Returns true if it is a text node with whitespaces only
jQuery.fn.isPhantomNode = function() {
    if (this[0].nodeType == 3)
        return !(/[^\t\n\r ]/.test(this[0].data));

    return false;
};

WYMeditor.isPhantomNode = function(n) {
    if (n.nodeType == 3)
        return !(/[^\t\n\r ]/.test(n.data));

    return false;
};

WYMeditor.isPhantomString = function(str) {
    return !(/[^\t\n\r ]/.test(str));
};

// Returns the Parents or the node itself
// jqexpr = a jQuery expression
jQuery.fn.parentsOrSelf = function(jqexpr) {
    var n = this;

    if (n[0].nodeType == 3)
        n = n.parents().slice(0, 1);

//  if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug)
    if (n.filter(jqexpr).size() == 1)
        return n;
    else
        return n.parents(jqexpr).slice(0, 1);
};

// String & array helpers

WYMeditor.Helper = {
    //replace all instances of 'old' by 'rep' in 'str' string
    replaceAll: function(str, old, rep) {
        var rExp = new RegExp(old, "g");
        return(str.replace(rExp, rep));
    },
    //insert 'inserted' at position 'pos' in 'str' string
    insertAt: function(str, inserted, pos) {
        return(str.substr(0, pos) + inserted + str.substring(pos));
    },
    //trim 'str' string
    trim: function(str) {
        return str.replace(/^(\s*)|(\s*)$/gm, '');
    },
    //return true if 'arr' array contains 'elem', or false
    contains: function(arr, elem) {
        for (var i = 0; i < arr.length; i++) {
            if (arr[i] === elem)
                return true;
        }
        return false;
    },
    //return 'item' position in 'arr' array, or -1
    indexOf: function(arr, item) {
        var ret = -1;
        for (var i = 0; i < arr.length; i++) {
            if (arr[i] == item) {
                ret = i;
                break;
            }
        }
        return(ret);
    },
    //return 'item' object in 'arr' array, checking its 'name' property, or null
    findByName: function(arr, name) {
        for (var i = 0; i < arr.length; i++) {
            var item = arr[i];
            if (item.name == name)
                return(item);
        }
        return(null);
    }
};


/*
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (c) 2005 - 2012 Jean-Francois Hovinne, http://www.wymeditor.org/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http://www.wymeditor.org/
 *
 * File Name:
 *        jquery.wymeditor.explorer.js
 *        MSIE specific class and functions.
 *        See the documentation for more info.
 *
 * File Authors:
 *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
 *        Bermi Ferrer (wymeditor a-t bermi dotorg)
 *        Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom)
 *        Jonatan Lundin (jonatan.lundin a-t gmail dotcom)
 */

WYMeditor.WymClassExplorer = function(wym) {

    this._wym = wym;
    this._class = "className";
    this._newLine = "\n";
};

WYMeditor.WymClassExplorer.prototype.initIframe = function(iframe) {

    //This function is executed twice, though it is called once!
    //But MSIE needs that, otherwise designMode won't work.
    //Weird.

    this._iframe = iframe;
    this._doc = iframe.contentWindow.document;

    //add css rules from options
    var styles = this._doc.styleSheets[0];
    var aCss = eval(this._options.editorStyles);

    this.addCssRules(this._doc, aCss);

    this._doc.title = this._wym._index;

    //set the text direction
    jQuery('html', this._doc).attr('dir', this._options.direction);

    //init html value
    jQuery(this._doc.body).html(this._wym._html);

    //handle events
    var wym = this;

    this._doc.body.onfocus = function()
    {
        wym._doc.designMode = "on";
        wym._doc = iframe.contentWindow.document;
    };
    this._doc.onbeforedeactivate = function() {
        wym.saveCaret();
    };
    this._doc.onkeyup = function() {
        wym.saveCaret();
        wym.keyup();
    };
    this._doc.onclick = function() {
        wym.saveCaret();
    };

    this._doc.body.onbeforepaste = function() {
        wym._iframe.contentWindow.event.returnValue = false;
    };

    this._doc.body.onpaste = function() {
        wym._iframe.contentWindow.event.returnValue = false;
        wym.paste(window.clipboardData.getData("Text"));
    };

    //callback can't be executed twice, so we check
    if (this._initialized) {

        //pre-bind functions
        if (jQuery.isFunction(this._options.preBind))
            this._options.preBind(this);

        //bind external events
        this._wym.bindEvents();

        //post-init functions
        if (jQuery.isFunction(this._options.postInit))
            this._options.postInit(this);

        //add event listeners to doc elements, e.g. images
        this.listen();
    }

    this._initialized = true;

    //init designMode
    this._doc.designMode = "on";
    try {
        // (bermi's note) noticed when running unit tests on IE6
        // Is this really needed, it trigger an unexisting property on IE6
        this._doc = iframe.contentWindow.document;
    } catch (e) {
    }
};

WYMeditor.WymClassExplorer.prototype._exec = function(cmd, param) {

    switch (cmd) {

        case WYMeditor.INDENT:
        case WYMeditor.OUTDENT:

            var container = this.findUp(this.container(), WYMeditor.LI);
            if (container) {
                var ancestor = container.parentNode.parentNode;
                if (container.parentNode.childNodes.length > 1
                        || ancestor.tagName.toLowerCase() == WYMeditor.OL
                        || ancestor.tagName.toLowerCase() == WYMeditor.UL)
                    this._doc.execCommand(cmd);
            }
            break;
        default:
            if (param)
                this._doc.execCommand(cmd, false, param);
            else
                this._doc.execCommand(cmd);
            break;
    }

};

WYMeditor.WymClassExplorer.prototype.selected = function() {

    var caretPos = this._iframe.contentWindow.document.caretPos;
    if (caretPos != null) {
        if (caretPos.parentElement != undefined)
            return(caretPos.parentElement());
    }
};

WYMeditor.WymClassExplorer.prototype.saveCaret = function() {

    this._doc.caretPos = this._doc.selection.createRange();
};

WYMeditor.WymClassExplorer.prototype.addCssRule = function(styles, oCss) {

    styles.addRule(oCss.name, oCss.css);
};

WYMeditor.WymClassExplorer.prototype.insert = function(html) {

    // Get the current selection
    var range = this._doc.selection.createRange();

    // Check if the current selection is inside the editor
    if (jQuery(range.parentElement()).parents(this._options.iframeBodySelector).is('*')) {
        try {
            // Overwrite selection with provided html
            range.pasteHTML(html);
        } catch (e) {
        }
    } else {
        // Fall back to the internal paste function if there's no selection
        this.paste(html);
    }
};

WYMeditor.WymClassExplorer.prototype.wrap = function(left, right) {

    // Get the current selection
    var range = this._doc.selection.createRange();

    // Check if the current selection is inside the editor
    if (jQuery(range.parentElement()).parents(this._options.iframeBodySelector).is('*')) {
        try {
            // Overwrite selection with provided html
            range.pasteHTML(left + range.text + right);
        } catch (e) {
        }
    }
};

WYMeditor.WymClassExplorer.prototype.unwrap = function() {

    // Get the current selection
    var range = this._doc.selection.createRange();

    // Check if the current selection is inside the editor
    if (jQuery(range.parentElement()).parents(this._options.iframeBodySelector).is('*')) {
        try {
            // Unwrap selection
            var text = range.text;
            this._exec('Cut');
            range.pasteHTML(text);
        } catch (e) {
        }
    }
};

//keyup handler
WYMeditor.WymClassExplorer.prototype.keyup = function() {
    this._selected_image = null;
};

WYMeditor.WymClassExplorer.prototype.setFocusToNode = function(node, toStart) {
    switch (jQuery.browser.version) {
        case '6.0':
        case '7.0':
            var range = this._doc.selection.createRange();
            toStart = toStart ? true : false;
            range.moveToElementText(node);
            range.collapse(toStart);
            range.select();
            node.focus();
            break;
        case '8.0':
            //TODO: broken in IE8
            break;
        default:
            var range = this._doc.createRange(),
                    selection = this._iframe.contentWindow.getSelection();
            toStart = toStart ? 0 : 1;
            range.selectNodeContents(node);
            selection.addRange(range);
            selection.collapse(node, toStart);
            this._iframe.contentWindow.focus();
    }
};
/*
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (c) 2005 - 2012 Jean-Francois Hovinne, http://www.wymeditor.org/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http://www.wymeditor.org/
 *
 * File Name:
 *        jquery.wymeditor.mozilla.js
 *        Gecko specific class and functions.
 *        See the documentation for more info.
 *
 * File Authors:
 *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
 *        Volker Mische (vmx a-t gmx dotde)
 *        Bermi Ferrer (wymeditor a-t bermi dotorg)
 *        Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom)
 *        Jonatan Lundin (jonatan.lundin a-t gmail dotcom)
 */

WYMeditor.WymClassMozilla = function(wym) {

    this._wym = wym;
    this._class = "class";
    this._newLine = "\n";
};

WYMeditor.WymClassMozilla.prototype.initIframe = function(iframe) {
    var wym = this;

    this._iframe = iframe;
    this._doc = iframe.contentDocument;

    //add css rules from options

    var styles = this._doc.styleSheets[0];
    var aCss = eval(this._options.editorStyles);

    this.addCssRules(this._doc, aCss);

    this._doc.title = this._wym._index;

    //set the text direction
    jQuery('html', this._doc).attr('dir', this._options.direction);

    //init html value
    this.html(this._wym._html);

    //init designMode
    this.enableDesignMode();

    //pre-bind functions
    if (jQuery.isFunction(this._options.preBind))
        this._options.preBind(this);

    //bind external events
    this._wym.bindEvents();

    //bind editor keydown events
    jQuery(this._doc).bind("keydown", this.keydown);

    //bind editor keyup events
    jQuery(this._doc).bind("keyup", this.keyup);

    //bind editor focus events (used to reset designmode - Gecko bug)
    jQuery(this._doc).bind("focus", function() {
        // Fix scope
        wym.enableDesignMode.call(wym);
    });

    //post-init functions
    if (jQuery.isFunction(this._options.postInit))
        this._options.postInit(this);

    //add event listeners to doc elements, e.g. images
    this.listen();
};

/* @name html
 * @description Get/Set the html value
 */
WYMeditor.WymClassMozilla.prototype.html = function(html) {

    if (typeof html === 'string') {

        //disable designMode
        try {
            this._doc.designMode = "off";
        } catch (e) {
        }
        ;

        //replace em by i and strong by bold
        //(designMode issue)
        html = html.replace(/<em(\b[^>]*)>/gi, "<i$1>")
                .replace(/<\/em>/gi, "</i>")
                .replace(/<strong(\b[^>]*)>/gi, "<b$1>")
                .replace(/<\/strong>/gi, "</b>");

        //update the html body
        jQuery(this._doc.body).html(html);

        //re-init designMode
        this.enableDesignMode();
    }
    else
        return(jQuery(this._doc.body).html());
};

WYMeditor.WymClassMozilla.prototype._exec = function(cmd, param) {

    if (!this.selected())
        return(false);

    switch (cmd) {

        case WYMeditor.INDENT:
        case WYMeditor.OUTDENT:

            var focusNode = this.selected();
            var sel = this._iframe.contentWindow.getSelection();
            var anchorNode = sel.anchorNode;
            if (anchorNode.nodeName == "#text")
                anchorNode = anchorNode.parentNode;

            focusNode = this.findUp(focusNode, WYMeditor.BLOCKS);
            anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS);

            if (focusNode && focusNode == anchorNode
                    && focusNode.tagName.toLowerCase() == WYMeditor.LI) {

                var ancestor = focusNode.parentNode.parentNode;

                if (focusNode.parentNode.childNodes.length > 1
                        || ancestor.tagName.toLowerCase() == WYMeditor.OL
                        || ancestor.tagName.toLowerCase() == WYMeditor.UL)
                    this._doc.execCommand(cmd, '', null);
            }

            break;

        default:

            if (param)
                this._doc.execCommand(cmd, '', param);
            else
                this._doc.execCommand(cmd, '', null);
    }

    //set to P if parent = BODY
    var container = this.selected();
    if (container.tagName.toLowerCase() == WYMeditor.BODY)
        this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
};

/* @name selected
 * @description Returns the selected container
 */
WYMeditor.WymClassMozilla.prototype.selected = function() {

    var sel = this._iframe.contentWindow.getSelection();
    var node = sel.focusNode;
    if (node) {
        if (node.nodeName == "#text")
            return(node.parentNode);
        else
            return(node);
    } else
        return(null);
};

WYMeditor.WymClassMozilla.prototype.addCssRule = function(styles, oCss) {

    styles.insertRule(oCss.name + " {" + oCss.css + "}",
            styles.cssRules.length);
};


//keydown handler, mainly used for keyboard shortcuts
WYMeditor.WymClassMozilla.prototype.keydown = function(evt) {

    //'this' is the doc
    var wym = WYMeditor.INSTANCES[this.title];
    var container = null;

    if (evt.ctrlKey) {
        if (evt.keyCode == 66) {
            //CTRL+b => STRONG
            wym._exec(WYMeditor.BOLD);
            return false;
        }
        if (evt.keyCode == 73) {
            //CTRL+i => EMPHASIS
            wym._exec(WYMeditor.ITALIC);
            return false;
        }
    }

    else if (evt.keyCode == 13) {
        if (!evt.shiftKey) {
            //fix PRE bug #73
            container = wym.selected();
            if (container && container.tagName.toLowerCase() == WYMeditor.PRE) {
                evt.preventDefault();
                wym.insert('<p></p>');
            }
        }
    }
};

//keyup handler, mainly used for cleanups
WYMeditor.WymClassMozilla.prototype.keyup = function(evt) {

    //'this' is the doc
    var wym = WYMeditor.INSTANCES[this.title];

    wym._selected_image = null;
    var container = null;

    if (evt.keyCode == 13 && !evt.shiftKey) {

        //RETURN key
        //cleanup <br><br> between paragraphs
        jQuery(wym._doc.body).children(WYMeditor.BR).remove();
    }

    if (evt.keyCode != 8
            && evt.keyCode != 17
            && evt.keyCode != 46
            && evt.keyCode != 224
            && !evt.metaKey
            && !evt.ctrlKey) {

        //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
        //text nodes replaced by P

        container = wym.selected();
        var name = container.tagName.toLowerCase();

        //fix forbidden main containers
        if (
                name == "strong" ||
                name == "b" ||
                name == "em" ||
                name == "i" ||
                name == "sub" ||
                name == "sup" ||
                name == "a"

                )
            name = container.parentNode.tagName.toLowerCase();

        if (name == WYMeditor.BODY)
            wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
    }
};

WYMeditor.WymClassMozilla.prototype.enableDesignMode = function() {
    if (this._doc.designMode == "off") {
        try {
            this._doc.designMode = "on";
            this._doc.execCommand("styleWithCSS", '', false);
        } catch (e) {
        }
    }
};

WYMeditor.WymClassMozilla.prototype.openBlockTag = function(tag, attributes)
{
    var attributes = this.validator.getValidTagAttributes(tag, attributes);

    // Handle Mozilla styled spans
    if (tag == 'span' && attributes.style) {
        var new_tag = this.getTagForStyle(attributes.style);
        if (new_tag) {
            this._tag_stack.pop();
            var tag = new_tag;
            this._tag_stack.push(new_tag);
            attributes.style = '';
        } else {
            return;
        }
    }

    this.output += this.helper.tag(tag, attributes, true);
};

WYMeditor.WymClassMozilla.prototype.getTagForStyle = function(style) {

    if (/bold/.test(style))
        return 'strong';
    if (/italic/.test(style))
        return 'em';
    if (/sub/.test(style))
        return 'sub';
    if (/super/.test(style))
        return 'sup';
    return false;
};

/*
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (c) 2005 - 2012 Jean-Francois Hovinne, http://www.wymeditor.org/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http://www.wymeditor.org/
 *
 * File Name:
 *        jquery.wymeditor.opera.js
 *        Opera specific class and functions.
 *        See the documentation for more info.
 *
 * File Authors:
 *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
 */

WYMeditor.WymClassOpera = function(wym) {

    this._wym = wym;
    this._class = "class";
    this._newLine = "\r\n";
};

WYMeditor.WymClassOpera.prototype.initIframe = function(iframe) {

    this._iframe = iframe;
    this._doc = iframe.contentWindow.document;

    //add css rules from options
    var styles = this._doc.styleSheets[0];
    var aCss = eval(this._options.editorStyles);

    this.addCssRules(this._doc, aCss);

    this._doc.title = this._wym._index;

    //set the text direction
    jQuery('html', this._doc).attr('dir', this._options.direction);

    //init designMode
    this._doc.designMode = "on";

    //init html value
    this.html(this._wym._html);

    //pre-bind functions
    if (jQuery.isFunction(this._options.preBind))
        this._options.preBind(this);

    //bind external events
    this._wym.bindEvents();

    //bind editor keydown events
    jQuery(this._doc).bind("keydown", this.keydown);

    //bind editor events
    jQuery(this._doc).bind("keyup", this.keyup);

    //post-init functions
    if (jQuery.isFunction(this._options.postInit))
        this._options.postInit(this);

    //add event listeners to doc elements, e.g. images
    this.listen();
};

WYMeditor.WymClassOpera.prototype._exec = function(cmd, param) {

    if (param)
        this._doc.execCommand(cmd, false, param);
    else
        this._doc.execCommand(cmd);

};

WYMeditor.WymClassOpera.prototype.selected = function() {

    var sel = this._iframe.contentWindow.getSelection();
    var node = sel.focusNode;
    if (node) {
        if (node.nodeName == "#text")
            return(node.parentNode);
        else
            return(node);
    } else
        return(null);
};

WYMeditor.WymClassOpera.prototype.addCssRule = function(styles, oCss) {

    styles.insertRule(oCss.name + " {" + oCss.css + "}",
            styles.cssRules.length);
};

//keydown handler
WYMeditor.WymClassOpera.prototype.keydown = function(evt) {

    //'this' is the doc
    var wym = WYMeditor.INSTANCES[this.title];
    var sel = wym._iframe.contentWindow.getSelection();
    startNode = sel.getRangeAt(0).startContainer;

    //Get a P instead of no container
    if (!jQuery(startNode).parentsOrSelf(
            WYMeditor.MAIN_CONTAINERS.join(","))[0]
            && !jQuery(startNode).parentsOrSelf('li')
            && evt.keyCode != WYMeditor.KEY.ENTER
            && evt.keyCode != WYMeditor.KEY.LEFT
            && evt.keyCode != WYMeditor.KEY.UP
            && evt.keyCode != WYMeditor.KEY.RIGHT
            && evt.keyCode != WYMeditor.KEY.DOWN
            && evt.keyCode != WYMeditor.KEY.BACKSPACE
            && evt.keyCode != WYMeditor.KEY.DELETE)
        wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);

};

//keyup handler
WYMeditor.WymClassOpera.prototype.keyup = function(evt) {

    //'this' is the doc
    var wym = WYMeditor.INSTANCES[this.title];
    wym._selected_image = null;
};
/*
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (c) 2005 - 2012 Jean-Francois Hovinne, http://www.wymeditor.org/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http://www.wymeditor.org/
 *
 * File Name:
 *        jquery.wymeditor.safari.js
 *        Safari specific class and functions.
 *        See the documentation for more info.
 *
 * File Authors:
 *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
 *        Scott Lewis (lewiscot a-t gmail dotcom)
 */

WYMeditor.WymClassSafari = function(wym) {

    this._wym = wym;
    this._class = "class";
    this._newLine = "\n";
};

WYMeditor.WymClassSafari.prototype.initIframe = function(iframe) {

    this._iframe = iframe;
    this._doc = iframe.contentDocument;

    //add css rules from options

    var styles = this._doc.styleSheets[0];
    var aCss = eval(this._options.editorStyles);

    this.addCssRules(this._doc, aCss);

    this._doc.title = this._wym._index;

    //set the text direction
    jQuery('html', this._doc).attr('dir', this._options.direction);

    //init designMode
    this._doc.designMode = "on";

    //init html value
    this.html(this._wym._html);

    //pre-bind functions
    if (jQuery.isFunction(this._options.preBind))
        this._options.preBind(this);

    //bind external events
    this._wym.bindEvents();

    //bind editor keydown events
    jQuery(this._doc).bind("keydown", this.keydown);

    //bind editor keyup events
    jQuery(this._doc).bind("keyup", this.keyup);

    //post-init functions
    if (jQuery.isFunction(this._options.postInit))
        this._options.postInit(this);

    //add event listeners to doc elements, e.g. images
    this.listen();
};

WYMeditor.WymClassSafari.prototype._exec = function(cmd, param) {

    if (!this.selected())
        return(false);

    switch (cmd) {

        case WYMeditor.INDENT:
        case WYMeditor.OUTDENT:

            var focusNode = this.selected();
            var sel = this._iframe.contentWindow.getSelection();
            var anchorNode = sel.anchorNode;
            if (anchorNode.nodeName == "#text")
                anchorNode = anchorNode.parentNode;

            focusNode = this.findUp(focusNode, WYMeditor.BLOCKS);
            anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS);

            if (focusNode && focusNode == anchorNode
                    && focusNode.tagName.toLowerCase() == WYMeditor.LI) {

                var ancestor = focusNode.parentNode.parentNode;

                if (focusNode.parentNode.childNodes.length > 1
                        || ancestor.tagName.toLowerCase() == WYMeditor.OL
                        || ancestor.tagName.toLowerCase() == WYMeditor.UL)
                    this._doc.execCommand(cmd, '', null);
            }

            break;

        case WYMeditor.INSERT_ORDEREDLIST:
        case WYMeditor.INSERT_UNORDEREDLIST:

            this._doc.execCommand(cmd, '', null);

            //Safari creates lists in e.g. paragraphs.
            //Find the container, and remove it.
            var focusNode = this.selected();
            var container = this.findUp(focusNode, WYMeditor.MAIN_CONTAINERS);
            if (container)
                jQuery(container).replaceWith(jQuery(container).html());

            break;

        default:

            if (param)
                this._doc.execCommand(cmd, '', param);
            else
                this._doc.execCommand(cmd, '', null);
    }

    //set to P if parent = BODY
    var container = this.selected();
    if (container && container.tagName.toLowerCase() == WYMeditor.BODY)
        this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);

};

/* @name selected
 * @description Returns the selected container
 */
WYMeditor.WymClassSafari.prototype.selected = function() {

    var sel = this._iframe.contentWindow.getSelection();
    var node = sel.focusNode;
    if (node) {
        if (node.nodeName == "#text")
            return(node.parentNode);
        else
            return(node);
    } else
        return(null);
};

WYMeditor.WymClassSafari.prototype.addCssRule = function(styles, oCss) {

    styles.insertRule(oCss.name + " {" + oCss.css + "}",
            styles.cssRules.length);
};


//keydown handler, mainly used for keyboard shortcuts
WYMeditor.WymClassSafari.prototype.keydown = function(evt) {

    //'this' is the doc
    var wym = WYMeditor.INSTANCES[this.title];

    if (evt.ctrlKey) {
        if (evt.keyCode == 66) {
            //CTRL+b => STRONG
            wym._exec(WYMeditor.BOLD);
            return false;
        }
        if (evt.keyCode == 73) {
            //CTRL+i => EMPHASIS
            wym._exec(WYMeditor.ITALIC);
            return false;
        }
    }
};

//keyup handler, mainly used for cleanups
WYMeditor.WymClassSafari.prototype.keyup = function(evt) {

    //'this' is the doc
    var wym = WYMeditor.INSTANCES[this.title];

    wym._selected_image = null;
    var container = null;

    if (evt.keyCode == 13 && !evt.shiftKey) {

        //RETURN key
        //cleanup <br><br> between paragraphs
        jQuery(wym._doc.body).children(WYMeditor.BR).remove();

        //fix PRE bug #73
        container = wym.selected();
        if (container && container.tagName.toLowerCase() == WYMeditor.PRE)
            wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //create P after PRE
    }

    //fix #112
    if (evt.keyCode == 13 && evt.shiftKey) {
        wym._exec('InsertLineBreak');
    }

    if (evt.keyCode != 8
            && evt.keyCode != 17
            && evt.keyCode != 46
            && evt.keyCode != 224
            && !evt.metaKey
            && !evt.ctrlKey) {

        //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
        //text nodes replaced by P

        container = wym.selected();
        var name = container.tagName.toLowerCase();

        //fix forbidden main containers
        if (
                name == "strong" ||
                name == "b" ||
                name == "em" ||
                name == "i" ||
                name == "sub" ||
                name == "sup" ||
                name == "a" ||
                name == "span" //fix #110

                )
            name = container.parentNode.tagName.toLowerCase();

        if (name == WYMeditor.BODY || name == WYMeditor.DIV)
            wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //fix #110 for DIV
    }
};

WYMeditor.WymClassSafari.prototype.openBlockTag = function(tag, attributes)
{
    var attributes = this.validator.getValidTagAttributes(tag, attributes);

    // Handle Safari styled spans
    if (tag == 'span' && attributes.style) {
        var new_tag = this.getTagForStyle(attributes.style);
        if (new_tag) {
            this._tag_stack.pop();
            var tag = new_tag;
            this._tag_stack.push(new_tag);
            attributes.style = '';

            //should fix #125 - also removed the xhtml() override
            if (typeof attributes['class'] == 'string')
                attributes['class'] = attributes['class'].replace(/apple-style-span/gi, '');

        } else {
            return;
        }
    }

    this.output += this.helper.tag(tag, attributes, true);
};

WYMeditor.WymClassSafari.prototype.getTagForStyle = function(style) {

    if (/bold/.test(style))
        return 'strong';
    if (/italic/.test(style))
        return 'em';
    if (/sub/.test(style))
        return 'sub';
    if (/super/.test(style))
        return 'sup';
    return false;
};
